/*-------------------------------------------------------------------------
 *
 * equivclass.c
 *	  管理等价类的例程
 *
 * 有关等价类的讨论，请参见src/backend/optimizer/README。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/path/equivclass.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "access/stratnum.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "utils/lsyscache.h"


static EquivalenceMember *add_eq_member(EquivalenceClass *fc_ec,
										Expr *fc_expr, Relids fc_relids, Relids fc_nullable_relids,
										bool fc_is_child, Oid fc_datatype);
static void fc_generate_base_implied_equalities_const(PlannerInfo *fc_root,
												   EquivalenceClass *fc_ec);
static void fc_generate_base_implied_equalities_no_const(PlannerInfo *fc_root,
													  EquivalenceClass *fc_ec);
static void fc_generate_base_implied_equalities_broken(PlannerInfo *fc_root,
													EquivalenceClass *fc_ec);
static List *fc_generate_join_implied_equalities_normal(PlannerInfo *fc_root,
													 EquivalenceClass *fc_ec,
													 Relids fc_join_relids,
													 Relids fc_outer_relids,
													 Relids fc_inner_relids);
static List *fc_generate_join_implied_equalities_broken(PlannerInfo *fc_root,
													 EquivalenceClass *fc_ec,
													 Relids fc_nominal_join_relids,
													 Relids fc_outer_relids,
													 Relids fc_nominal_inner_relids,
													 RelOptInfo *fc_inner_rel);
static Oid	fc_select_equality_operator(EquivalenceClass *fc_ec,
									 Oid fc_lefttype, Oid fc_righttype);
static RestrictInfo *fc_create_join_clause(PlannerInfo *fc_root,
										EquivalenceClass *fc_ec, Oid fc_opno,
										EquivalenceMember *fc_leftem,
										EquivalenceMember *fc_rightem,
										EquivalenceClass *fc_parent_ec);
static bool fc_reconsider_outer_join_clause(PlannerInfo *fc_root,
										 RestrictInfo *fc_rinfo,
										 bool fc_outer_on_left);
static bool fc_reconsider_full_join_clause(PlannerInfo *fc_root,
										RestrictInfo *fc_rinfo);
static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *fc_root,
												Relids fc_relids);
static Bitmapset *get_common_eclass_indexes(PlannerInfo *fc_root, Relids fc_relids1,
											Relids fc_relids2);


/*
 * process_equivalence
 *	  给定的子句具有可合并连接的操作符，且可以通过外连接无延迟地应用，因此它的两侧在可计算时可以被视为相等；
 *	  此外，这种相等性可以传递扩展。如果适用，请在EquivalenceClass数据结构中记录这一知识。如果成功返回true，
 *	  否则返回false（在这种情况下，调用者应将该子句视为普通子句，而非等价子句）。
 *
 * 在某些情况下，尽管我们无法将子句转换为EquivalenceClass知识，但仍然可以将其修改为比原始形式更有用的形式。
 * 然后，*p_restrictinfo将被一个新的RestrictInfo替换，调用者应该使用该RestrictInfo进行进一步处理。
 *
 * 如果below_outer_join为真，则表示该子句在外连接的可空侧下找到，因此其两侧可能有效地都为NULL，而不是严格相等。
 * 在这种情况下，我们仍然可以推断相等性，但要注意标记任何来自此类子句的EquivalenceClass。此外，
 * 我们必须检查双方要么为伪常量，要么为Vars的严格函数，否则在外连接上方它们可能都不会转为NULL。
 * （这就是我们需要失败返回的主要原因。在此处检查此情况比在调用点更方便…）
 *
 * 我们还拒绝包含泄漏函数且安全级别高于零的提议等价子句。EC评估规则要求我们在某些连接级别应用特定测试，
 * 而且我们无法容忍因安全级别原因延迟任何测试。通过拒绝可能需要安全延迟的候选子句，我们可以确保在被应用时
 * 安全地应用EC子句。
 *
 * 成功返回时，我们还初始化了子句的left_ec/right_ec字段，以指向表示该子句的EquivalenceClass。这将节省后续的查找努力。
 *
 * 注意：构建合并的EquivalenceClasses是一个标准的并查集问题，对于此问题存在比简单列表更好的数据结构。
 * 如果这段代码最终证明是瓶颈，那么可以加速---但现在，简单即美。
 *
 * 注意：这仅在计划者启动期间调用，而不是在GEQO探索期间，因此我们不必担心是否处于正确的内存上下文。
 */
bool process_equivalence(PlannerInfo *fc_root,
					RestrictInfo **fc_p_restrictinfo,
					bool fc_below_outer_join)
{
	RestrictInfo *fc_restrictinfo = *fc_p_restrictinfo;
	Expr	   *fc_clause = fc_restrictinfo->clause;
	Oid			fc_opno,
				fc_collation,
				fc_item1_type,
				fc_item2_type;
	Expr	   *fc_item1;
	Expr	   *fc_item2;
	Relids		fc_item1_relids,
				fc_item2_relids,
				fc_item1_nullable_relids,
				fc_item2_nullable_relids;
	List	   *fc_opfamilies;
	EquivalenceClass *fc_ec1,
			   *fc_ec2;
	EquivalenceMember *fc_em1,
			   *fc_em2;
	ListCell   *lc1;
	int			fc_ec2_idx;

	/* 不应已经标记为生成了eclass */
	Assert(fc_restrictinfo->left_ec == NULL);
	Assert(fc_restrictinfo->right_ec == NULL);

	/* 如果由于安全考虑可能会被推迟，则拒绝 */
	if (fc_restrictinfo->security_level > 0 && !fc_restrictinfo->leakproof)
		return false;

	/* 从给定子句中提取信息 */
	Assert(is_opclause(fc_clause));
	fc_opno = ((OpExpr *) fc_clause)->opno;
	fc_collation = ((OpExpr *) fc_clause)->inputcollid;
	fc_item1 = (Expr *) get_leftop(fc_clause);
	fc_item2 = (Expr *) get_rightop(fc_clause);
	fc_item1_relids = fc_restrictinfo->left_relids;
	fc_item2_relids = fc_restrictinfo->right_relids;

	/*
	 * 确保两个输入表达式暴露所需的排序（它们的类型应该已经没问题）；详见canonicalize_ec_expression的注释。
	 */
	fc_item1 = canonicalize_ec_expression(fc_item1,
									   exprType((Node *) fc_item1),
									   fc_collation);
	fc_item2 = canonicalize_ec_expression(fc_item2,
									   exprType((Node *) fc_item2),
									   fc_collation);

	/*
	 * 形式为X=X的子句无法转换为EquivalenceClasses。
	 * 我们要么会得到一个单项的EC，失去该子句存在的知识，要么会生成一个具有重复条目的EC，导致其他问题。
	 */
	if (equal(fc_item1, fc_item2))
	{
		
/*
		 * 如果操作符是严格的，那么该子句可以被视为仅仅是
		 * "X IS NOT NULL"。 （由于我们知道考虑的是顶层的
		 * 限制条件，因此可以忽略 FALSE 和 NULL 结果之间的区别。）
		 * 进行这种转换是有意义的，因为我们通常会得到比 X=X
		 * 更好的选择性估计。
		 *
		 * 如果操作符不是严格的，我们无法确定它将如何处理 
		 * NULL，因此不要尝试对其进行优化。
		 */
		set_opfuncid((OpExpr *) fc_clause);
		if (func_strict(((OpExpr *) fc_clause)->opfuncid))
		{
			NullTest   *fc_ntest = makeNode(NullTest);

			fc_ntest->arg = fc_item1;
			fc_ntest->nulltesttype = IS_NOT_NULL;
			fc_ntest->argisrow = false;	/* 即使复合参数也是正确的 */
			fc_ntest->location = -1;

			*fc_p_restrictinfo =
				make_restrictinfo(fc_root,
								  (Expr *) fc_ntest,
								  fc_restrictinfo->is_pushed_down,
								  fc_restrictinfo->outerjoin_delayed,
								  fc_restrictinfo->pseudoconstant,
								  fc_restrictinfo->security_level,
								  NULL,
								  fc_restrictinfo->outer_relids,
								  fc_restrictinfo->nullable_relids);
		}
		return false;
	}

	/*
	 * 如果在外连接下，检查严格性，否则拒绝。
	 */
	if (fc_below_outer_join)
	{
		if (!bms_is_empty(fc_item1_relids) &&
			contain_nonstrict_functions((Node *) fc_item1))
			return false;		/* 左侧是非严格的，但不是常量 */
		if (!bms_is_empty(fc_item2_relids) &&
			contain_nonstrict_functions((Node *) fc_item2))
			return false;		/* 右侧是非严格的，但不是常量 */
	}

	/* 为子句的每一侧计算可空关系 ID 集 */
	fc_item1_nullable_relids = bms_intersect(fc_item1_relids,
										  fc_restrictinfo->nullable_relids);
	fc_item2_nullable_relids = bms_intersect(fc_item2_relids,
										  fc_restrictinfo->nullable_relids);

	/*
	 * 我们使用操作符声明的输入类型，而不是输入的 exprType()，
	 * 作为 opfamily 查找的名义数据类型。这假设
	 * btree 操作符总是以 amoplefttype 和
	 * amoprighttype 等于其声明的输入类型进行注册。我们无论如何
	 * 需要这些信息来构建 EquivalenceMember 节点，通过现在提取它
	 * 我们可以使用类型比较来短路一些 equal() 测试。
	 */
	op_input_types(fc_opno, &fc_item1_type, &fc_item2_type);

	fc_opfamilies = fc_restrictinfo->mergeopfamilies;

	/*
	 * 在现有的 EquivalenceClasses 中搜索与
	 * item1 和 item2 匹配的项。这些是可能的结果：
	 *
	 * 1. 我们发现两者在同一个 EC 中。等价性已经知道，因此
	 * 没有什么可做的。
	 *
	 * 2. 我们发现两者在不同的 EC 中。将这两个 EC 合并在一起。
	 *
	 * 3. 我们只找到一个。将另一个添加到其 EC。
	 *
	 * 4. 我们两者都没有找到。创建一个新的、包含两个条目的 EC。
	 *
	 * 注意：由于所有 EC 都是通过此过程或类似的
	 * get_eclass_for_sort_expr() 搜索构建的，匹配的
	 * 项在多个现有的非易失性 EC 中是不可能的。因此，可以在
	 * 第一个匹配处停止检测。
	 */
	fc_ec1 = fc_ec2 = NULL;
	fc_em1 = fc_em2 = NULL;
	fc_ec2_idx = -1;
	foreach(lc1, fc_root->eq_classes)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) lfirst(lc1);
		ListCell   *fc_lc2;

		/* 永远不要匹配到易变的 EC */
		if (fc_cur_ec->ec_has_volatile)
			continue;

		/*
		 * 排序规则必须匹配；首先检查这一点，因为这比操作符
		 * 家族比较更便宜。
		 */
		if (fc_collation != fc_cur_ec->ec_collation)
			continue;

		/*
		 * “匹配”要求匹配一组 btree opfamilies。使用
		 * equal() 进行此测试在 get_mergejoin_opfamilies()
		 * 的注释中讨论了影响。
		 */
		if (!equal(fc_opfamilies, fc_cur_ec->ec_opfamilies))
			continue;

		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc2);

			Assert(!fc_cur_em->em_is_child);	/* 还没有子节点 */

			/*
			 * 如果在外连接下，不要匹配常量：它们看起来并不像
			 * 表现那么常量。
			 */
			if ((fc_below_outer_join || fc_cur_ec->ec_below_outer_join) &&
				fc_cur_em->em_is_const)
				continue;

			if (!fc_ec1 &&
				fc_item1_type == fc_cur_em->em_datatype &&
				equal(fc_item1, fc_cur_em->em_expr))
			{
				fc_ec1 = fc_cur_ec;
				fc_em1 = fc_cur_em;
				if (fc_ec2)
					break;
			}

			if (!fc_ec2 &&
				fc_item2_type == fc_cur_em->em_datatype &&
				equal(fc_item2, fc_cur_em->em_expr))
			{
				fc_ec2 = fc_cur_ec;
				fc_ec2_idx = foreach_current_index(lc1);
				fc_em2 = fc_cur_em;
				if (fc_ec1)
					break;
			}
		}

		if (fc_ec1 && fc_ec2)
			break;
	}

	/* 扫描完成，我们发现了什么？ */

	if (fc_ec1 && fc_ec2)
	{
		/* 如果是情况 1，则没有任何操作，只需添加到源 */
		if (fc_ec1 == fc_ec2)
		{
			fc_ec1->ec_sources = lappend(fc_ec1->ec_sources, fc_restrictinfo);
			fc_ec1->ec_below_outer_join |= fc_below_outer_join;
			fc_ec1->ec_min_security = Min(fc_ec1->ec_min_security,
									   fc_restrictinfo->security_level);
			fc_ec1->ec_max_security = Max(fc_ec1->ec_max_security,
									   fc_restrictinfo->security_level);
			/* 将 RI 标记为与此 eclass 相关联 */
			fc_restrictinfo->left_ec = fc_ec1;
			fc_restrictinfo->right_ec = fc_ec1;
			/* 标记RI为可用，与这一对EMs一起 */
			fc_restrictinfo->left_em = fc_em1;
			fc_restrictinfo->right_em = fc_em2;
			return true;
		}

		/*
		 * 案例2：需要合并ec1和ec2。 在EC达到规范状态后，这不应该发生；
		 * 否则，路径键可能因为合并而被渲染为非规范，并且通过移除eq_classes列表条目，
		 * 关系eclass索引将被破坏。
		 */
		if (fc_root->ec_merging_done)
			elog(ERROR, "too late to merge equivalence classes");

		/*
		 * 我们将ec2的条目添加到ec1，然后将ec2的ec_merged链接指向
		 * ec1，并从eq_classes列表中删除ec2。我们不能简单地
		 * 删除ec2，因为这可能会在现有的PathKeys中留下悬空指针。
		 * 我们留下它并链接，以便合并后的EC可以被找到。
		 */
		fc_ec1->ec_members = list_concat(fc_ec1->ec_members, fc_ec2->ec_members);
		fc_ec1->ec_sources = list_concat(fc_ec1->ec_sources, fc_ec2->ec_sources);
		fc_ec1->ec_derives = list_concat(fc_ec1->ec_derives, fc_ec2->ec_derives);
		fc_ec1->ec_relids = bms_join(fc_ec1->ec_relids, fc_ec2->ec_relids);
		fc_ec1->ec_has_const |= fc_ec2->ec_has_const;
		/* 不需要设置has_volatile */
		fc_ec1->ec_below_outer_join |= fc_ec2->ec_below_outer_join;
		fc_ec1->ec_min_security = Min(fc_ec1->ec_min_security,
								   fc_ec2->ec_min_security);
		fc_ec1->ec_max_security = Max(fc_ec1->ec_max_security,
								   fc_ec2->ec_max_security);
		fc_ec2->ec_merged = fc_ec1;
		fc_root->eq_classes = list_delete_nth_cell(fc_root->eq_classes, fc_ec2_idx);
		/* 只是为了避免调试时与悬空指针的混淆： */
		fc_ec2->ec_members = NIL;
		fc_ec2->ec_sources = NIL;
		fc_ec2->ec_derives = NIL;
		fc_ec2->ec_relids = NULL;
		fc_ec1->ec_sources = lappend(fc_ec1->ec_sources, fc_restrictinfo);
		fc_ec1->ec_below_outer_join |= fc_below_outer_join;
		fc_ec1->ec_min_security = Min(fc_ec1->ec_min_security,
								   fc_restrictinfo->security_level);
		fc_ec1->ec_max_security = Max(fc_ec1->ec_max_security,
								   fc_restrictinfo->security_level);
		/* 将 RI 标记为与此 eclass 相关联 */
		fc_restrictinfo->left_ec = fc_ec1;
		fc_restrictinfo->right_ec = fc_ec1;
		/* 标记RI为可用，与这一对EMs一起 */
		fc_restrictinfo->left_em = fc_em1;
		fc_restrictinfo->right_em = fc_em2;
	}
	else if (fc_ec1)
	{
		/* 案例3：将item2添加到ec1 */
		fc_em2 = add_eq_member(fc_ec1, fc_item2, fc_item2_relids, fc_item2_nullable_relids,
							false, fc_item2_type);
		fc_ec1->ec_sources = lappend(fc_ec1->ec_sources, fc_restrictinfo);
		fc_ec1->ec_below_outer_join |= fc_below_outer_join;
		fc_ec1->ec_min_security = Min(fc_ec1->ec_min_security,
								   fc_restrictinfo->security_level);
		fc_ec1->ec_max_security = Max(fc_ec1->ec_max_security,
								   fc_restrictinfo->security_level);
		/* 将 RI 标记为与此 eclass 相关联 */
		fc_restrictinfo->left_ec = fc_ec1;
		fc_restrictinfo->right_ec = fc_ec1;
		/* 标记RI为可用，与这一对EMs一起 */
		fc_restrictinfo->left_em = fc_em1;
		fc_restrictinfo->right_em = fc_em2;
	}
	else if (fc_ec2)
	{
		/* 案例3：将item1添加到ec2 */
		fc_em1 = add_eq_member(fc_ec2, fc_item1, fc_item1_relids, fc_item1_nullable_relids,
							false, fc_item1_type);
		fc_ec2->ec_sources = lappend(fc_ec2->ec_sources, fc_restrictinfo);
		fc_ec2->ec_below_outer_join |= fc_below_outer_join;
		fc_ec2->ec_min_security = Min(fc_ec2->ec_min_security,
								   fc_restrictinfo->security_level);
		fc_ec2->ec_max_security = Max(fc_ec2->ec_max_security,
								   fc_restrictinfo->security_level);
		/* 将 RI 标记为与此 eclass 相关联 */
		fc_restrictinfo->left_ec = fc_ec2;
		fc_restrictinfo->right_ec = fc_ec2;
		/* 标记RI为可用，与这一对EMs一起 */
		fc_restrictinfo->left_em = fc_em1;
		fc_restrictinfo->right_em = fc_em2;
	}
	else
	{
		/* 案例4：创建一个新的、两个条目的EC */
		EquivalenceClass *fc_ec = makeNode(EquivalenceClass);

		fc_ec->ec_opfamilies = fc_opfamilies;
		fc_ec->ec_collation = fc_collation;
		fc_ec->ec_members = NIL;
		fc_ec->ec_sources = list_make1(fc_restrictinfo);
		fc_ec->ec_derives = NIL;
		fc_ec->ec_relids = NULL;
		fc_ec->ec_has_const = false;
		fc_ec->ec_has_volatile = false;
		fc_ec->ec_below_outer_join = fc_below_outer_join;
		fc_ec->ec_broken = false;
		fc_ec->ec_sortref = 0;
		fc_ec->ec_min_security = fc_restrictinfo->security_level;
		fc_ec->ec_max_security = fc_restrictinfo->security_level;
		fc_ec->ec_merged = NULL;
		fc_em1 = add_eq_member(fc_ec, fc_item1, fc_item1_relids, fc_item1_nullable_relids,
							false, fc_item1_type);
		fc_em2 = add_eq_member(fc_ec, fc_item2, fc_item2_relids, fc_item2_nullable_relids,
							false, fc_item2_type);

		fc_root->eq_classes = lappend(fc_root->eq_classes, fc_ec);

		/* 将 RI 标记为与此 eclass 相关联 */
		fc_restrictinfo->left_ec = fc_ec;
		fc_restrictinfo->right_ec = fc_ec;
		/* 标记RI为可用，与这一对EMs一起 */
		fc_restrictinfo->left_em = fc_em1;
		fc_restrictinfo->right_em = fc_em2;
	}

	return true;
}

/*
 * canonicalize_ec_expression
 *
 * 此函数确保表达式暴露预期的类型和排序规则，以便它与其他
 * 应该相等的等价类表达式相等()。
 *
 * 数据类型的规则是暴露的类型应与EC的操作符家族的输入
 * 相匹配；这通常是操作符的声明输入类型，但在多态
 * 操作符的情况下，无需重新标记（比较parse_coerce.c的行为）。
 * 来自qual的表达式通常已经具有正确的类型，但来自
 * indexkeys的表达式可能没有（因为它们在pg_index中没有
 * 显示的重新标记），对于排序表达式也出现同样的问题
 * （因为解析器在对其放置重新标记时也同样草率）。这样的案例
 * 将与真实操作符二进制兼容，因此添加RelabelType是足够的。
 *
 * 此外，表达式的暴露排序规则必须与EC的排序规则匹配。
 * 这很重要，因为在像“foo < bar COLLATE baz”的比较中，
 * 只有一个表达式具有从解析器接收的正确暴露排序规则。
 * 强制它们都有此排序规则确保所有这种构造的变体拼写表现一致。
 * 再次，我们可以附加一个RelabelType来强制正确的暴露排序规则。
 * （在EC成员中完全不标记排序规则可能有效，但这很危险
 * 因为系统的某些部分期望exprCollation()能为排序键提供
 * 正确的答案。）
 */
Expr * canonicalize_ec_expression(Expr *fc_expr, Oid fc_req_type, Oid fc_req_collation)
{
	Oid			fc_expr_type = exprType((Node *) fc_expr);

	/*
	 * 对于多态输入类型的操作类，只需保持相同的暴露类型。
	 * RECORD操作类在这方面的工作方式与多态类型相同。
	 */
	if (IsPolymorphicType(fc_req_type) || fc_req_type == RECORDOID)
		fc_req_type = fc_expr_type;

	/*
	 * 如果表达式已经暴露正确的类型/排序规则，则不需要工作。
	 */
	if (fc_expr_type != fc_req_type ||
		exprCollation((Node *) fc_expr) != fc_req_collation)
	{
		/*
		 * 如果我们必须更改表达式的类型，请将typmod设置为-1，
		 * 因为新类型可能没有相同的typmod解释。
		 * 当我们只需要更改排序规则时，请保留暴露的typmod。
		 */
		int32		fc_req_typmod;

		if (fc_expr_type != fc_req_type)
			fc_req_typmod = -1;
		else
			fc_req_typmod = exprTypmod((Node *) fc_expr);

		/*
		 * 使用applyRelabelType，以便我们保持常量扁平性。
		 * 这很重要，因为eval_const_expressions已经被应用。
		 */
		fc_expr = (Expr *) applyRelabelType((Node *) fc_expr,
										 fc_req_type, fc_req_typmod, fc_req_collation,
										 COERCE_IMPLICIT_CAST, -1, false);
	}

	return fc_expr;
}

/*
 * add_eq_member - 构建一个新的EquivalenceMember并将其添加到EC
 */
static EquivalenceMember *
add_eq_member(EquivalenceClass *fc_ec, Expr *fc_expr, Relids fc_relids,
			  Relids fc_nullable_relids, bool fc_is_child, Oid fc_datatype)
{
	EquivalenceMember *fc_em = makeNode(EquivalenceMember);

	fc_em->em_expr = fc_expr;
	fc_em->em_relids = fc_relids;
	fc_em->em_nullable_relids = fc_nullable_relids;
	fc_em->em_is_const = false;
	fc_em->em_is_child = fc_is_child;
	fc_em->em_datatype = fc_datatype;

	if (bms_is_empty(fc_relids))
	{
		/*
		 * 没有变量，假设它是一个伪常量。对于从process_equivalence()
		 * 生成的条目，这是正确的，因为WHERE子句不能
		 * 包含聚合或SRF，并且在调用process_equivalence()之前
		 * 已检查非波动性。 但是
		 * get_eclass_for_sort_expr()必须更加努力。我们将测试放在
		 * 那里而不是这里以节省等价值的周期。
		 */
		Assert(!fc_is_child);
		fc_em->em_is_const = true;
		fc_ec->ec_has_const = true;
		/* 它不能影响ec_relids */
	}
	else if (!fc_is_child)			/* 子成员不会添加到ec_relids */
	{
		fc_ec->ec_relids = bms_add_members(fc_ec->ec_relids, fc_relids);
	}
	fc_ec->ec_members = lappend(fc_ec->ec_members, fc_em);

	return fc_em;
}



/*
 * get_eclass_for_sort_expr
 *	  给定一个表达式和操作族/排序信息，查找它所属的现有
 *	  等价类；如果没有，选择性地为其构建一个新的单成员
 *	  EquivalenceClass。
 *
 * expr 是表达式，nullable_relids 是潜在可空的基本 relids 的集合
 * 在其下方。我们实际上只关心表达式中使用的这类 relids 的集合；
 * 但为了方便调用者，我们在这里执行该交集步骤。调用者只需确保
 * nullable_relids 不会省略任何可能出现在 expr 中的可空关系。
 *
 * sortref 是来源于 SortGroupClause 的 SortGroupRef，如果没有则为零。
 * （如果表达式是波动的，它不应该是零！）
 *
 * 如果 rel 不为 NULL，它标识我们正在考虑路径的特定关系，
 * 并表示可以考虑该关系的子 EC 成员。否则子成员会被忽略。
 * （注意：由于子 EC 成员不保证唯一，非 NULL 值意味着可能存在多个
 * 匹配表达式的 EC；如果是这样，获得的便是有序依赖的。虽然这很烦人，
 * 但它只会在极端情况下发生，因此目前我们只能报告第一个匹配。
 * 还见 generate_implied_equalities_for_column 和 match_pathkeys_to_index。）
 *
 * 如果 create_it 为真，则在没有匹配时我们将构建一个新的
 * EquivalenceClass。如果 create_it 为假，则在没有匹配时我们只是返回 NULL。
 *
 * 这可以安全地在 EquivalenceClass 合并之前和之后使用；
 * 由于它从未导致合并，因此不会使任何现有的 EC
 * 或 PathKeys 无效。然而，在路径生成开始后添加的 EC
 * 可用性有限，因此通常最好提前创建它们。
 *
 * 注意：操作族必须与 process_equivalence() 的方式一致；
 * 即，从可合并的相等操作符生成。否则，我们可能无法检测到有效的
 * 等价关系，从而生成不良（但不不正确的）计划。
 */
EquivalenceClass *
get_eclass_for_sort_expr(PlannerInfo *fc_root,
						 Expr *fc_expr,
						 Relids fc_nullable_relids,
						 List *fc_opfamilies,
						 Oid fc_opcintype,
						 Oid fc_collation,
						 Index fc_sortref,
						 Relids fc_rel,
						 bool fc_create_it)
{
	Relids		fc_expr_relids;
	EquivalenceClass *fc_newec;
	EquivalenceMember *fc_newem;
	ListCell   *fc_lc1;
	MemoryContext fc_oldcontext;

	/*
	 * 确保表达式暴露正确的类型和排序。
	 */
	fc_expr = canonicalize_ec_expression(fc_expr, fc_opcintype, fc_collation);

	/*
	 * 扫描现有的 EquivalenceClasses 寻找匹配
	 */
	foreach(fc_lc1, fc_root->eq_classes)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) lfirst(fc_lc1);
		ListCell   *fc_lc2;

		/*
		 * 除非我们在查看另一个对同一波动 SortGroupClause 的引用，
		 * 否则决不匹配到波动的 EC。
		 */
		if (fc_cur_ec->ec_has_volatile &&
			(fc_sortref == 0 || fc_sortref != fc_cur_ec->ec_sortref))
			continue;

		if (fc_collation != fc_cur_ec->ec_collation)
			continue;
		if (!equal(fc_opfamilies, fc_cur_ec->ec_opfamilies))
			continue;

		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc2);

			/*
			 * 除非子成员匹配请求，否则忽略子成员。
			 */
			if (fc_cur_em->em_is_child &&
				!bms_equal(fc_cur_em->em_relids, fc_rel))
				continue;

			/*
			 * 如果在外连接下，不要匹配常量：它们看起来并不像
			 * 表现那么常量。
			 */
			if (fc_cur_ec->ec_below_outer_join &&
				fc_cur_em->em_is_const)
				continue;

			if (fc_opcintype == fc_cur_em->em_datatype &&
				equal(fc_expr, fc_cur_em->em_expr))
				return fc_cur_ec;	/* Match! */
		}
	}

	/* 无匹配；调用者是否想要一个 NULL 结果？ */
	if (!fc_create_it)
		return NULL;

	/*
	 * 好的，构建一个新的单成员 EC
	 *
	 * 在这里，我们必须确保在正确的上下文中构建 EC。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_root->planner_cxt);

	fc_newec = makeNode(EquivalenceClass);
	fc_newec->ec_opfamilies = list_copy(fc_opfamilies);
	fc_newec->ec_collation = fc_collation;
	fc_newec->ec_members = NIL;
	fc_newec->ec_sources = NIL;
	fc_newec->ec_derives = NIL;
	fc_newec->ec_relids = NULL;
	fc_newec->ec_has_const = false;
	fc_newec->ec_has_volatile = contain_volatile_functions((Node *) fc_expr);
	fc_newec->ec_below_outer_join = false;
	fc_newec->ec_broken = false;
	fc_newec->ec_sortref = fc_sortref;
	fc_newec->ec_min_security = UINT_MAX;
	fc_newec->ec_max_security = 0;
	fc_newec->ec_merged = NULL;

	if (fc_newec->ec_has_volatile && fc_sortref == 0) /* 不应该发生 */
		elog(ERROR, "volatile EquivalenceClass has no sortref");

	/*
	 * 获取在表达式中出现的可空 relids 的精确集合。
	 */
	fc_expr_relids = pull_varnos(fc_root, (Node *) fc_expr);
	fc_nullable_relids = bms_intersect(fc_nullable_relids, fc_expr_relids);

	fc_newem = add_eq_member(fc_newec, copyObject(fc_expr), fc_expr_relids,
						  fc_nullable_relids, false, fc_opcintype);

	/*
	 * add_eq_member 不检查波动函数、返回集函数、
	 * 聚合或窗口函数，但这些可能出现在排序表达式中；
	 * 因此我们必须检查其常量标记是否正确。
	 */
	if (fc_newec->ec_has_const)
	{
		if (fc_newec->ec_has_volatile ||
			expression_returns_set((Node *) fc_expr) ||
			contain_agg_clause((Node *) fc_expr) ||
			contain_window_function((Node *) fc_expr))
		{
			fc_newec->ec_has_const = false;
			fc_newem->em_is_const = false;
		}
	}

	fc_root->eq_classes = lappend(fc_root->eq_classes, fc_newec);

	/*
	 * 如果 EC 合并已完成，我们必须通过将新的
	 * EC 添加到其中提到的关系的 eclass_indexes 来清理。
	 */
	if (fc_root->ec_merging_done)
	{
		int			fc_ec_index = list_length(fc_root->eq_classes) - 1;
		int			fc_i = -1;

		while ((fc_i = bms_next_member(fc_newec->ec_relids, fc_i)) > 0)
		{
			RelOptInfo *fc_rel = fc_root->simple_rel_array[fc_i];

			Assert(fc_rel->reloptkind == RELOPT_BASEREL ||
				   fc_rel->reloptkind == RELOPT_DEADREL);

			fc_rel->eclass_indexes = bms_add_member(fc_rel->eclass_indexes,
												 fc_ec_index);
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_newec;
}

/*
 * find_ec_member_matching_expr
 *		定位匹配给定 expr 的 EquivalenceClass 成员（如果有）；
 *		如果没有匹配，则返回 NULL。
 *
 * “匹配”定义为“去掉 RelabelTypes 后相等”。
 * 这用于识别排序表达式，我们需要允许某些涉及二进制兼容
 * 排序操作符的情况进行二进制兼容的重新标记。
 *
 * 除非属于给定“relids”，否则忽略子 EC 成员。
 */
EquivalenceMember * find_ec_member_matching_expr(EquivalenceClass *fc_ec,
							 Expr *fc_expr,
							 Relids fc_relids)
{
	ListCell   *fc_lc;

	/* 我们忽略两端的二进制兼容重标记 */
	while (fc_expr && IsA(fc_expr, RelabelType))
		fc_expr = ((RelabelType *) fc_expr)->arg;

	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc);
		Expr	   *fc_emexpr;

		/*
		 * 我们不应该尝试通过包含常量的等价类排序，
		 * 所以不需要进一步考虑这种情况。
		 */
		if (fc_em->em_is_const)
			continue;

		/*
		 * 除非它们属于请求的关系，否则忽略子成员。
		 */
		if (fc_em->em_is_child &&
			!bms_is_subset(fc_em->em_relids, fc_relids))
			continue;

		/*
		 * 匹配如果相同的表达式（去掉重标记后）。
		 */
		fc_emexpr = fc_em->em_expr;
		while (fc_emexpr && IsA(fc_emexpr, RelabelType))
			fc_emexpr = ((RelabelType *) fc_emexpr)->arg;

		if (equal(fc_emexpr, fc_expr))
			return fc_em;
	}

	return NULL;
}

/*
 * find_computable_ec_member
 *		定位可以从 "exprs" 中出现的表达式计算得出的等价类成员；如果没有匹配，则返回 NULL。
 *
 * "exprs" 可以是一个光秃的表达式树列表，也可以是一个 TargetEntry 节点的列表。通常它会包含 Vars 和可能的 Aggrefs 以及 WindowFuncs；然而，在考虑一个附加关系成员时，该列表可以包含任意表达式。我们认为一个 EC 成员是可计算的，如果它所需的所有 Vars、PlaceHolderVars、Aggrefs 和 WindowFuncs 都存在于 "exprs" 中。
 *
 * 在该定义中有一些微妙之处：例如，如果一个 EC 成员是 Var_A + 1，而 "exprs" 中的是 Var_A + 2，它仍然是可计算的。之所以这样有效，是因为在最终的计划树中，EC 成员的表达式将作为与当前由 "exprs" 表示的相同计划节点的目标列表的一部分被计算。因此，如果我们有 Var_A 可用于现有目标列表成员，那么在 EC 表达式中使用它应该是可以的。
 *
 * 与 find_ec_member_matching_expr 不同，这里没有对二进制兼容重标记的特别规定。这是故意的：如果我们必须以这种方式计算一个表达式，setrefs.c 将坚决要求 Vars 和源目标列表的精确匹配。
 *
 * 除非子 EC 成员属于给定的 'relids'，否则将被忽略。此外，如果 'require_parallel_safe'，则忽略非并行安全的表达式。
 *
 * 注意：一些调用者出于符号原因将 root 传递为 NULL。当 require_parallel_safe 为假时，这是可以的。
 */
EquivalenceMember * find_computable_ec_member(PlannerInfo *fc_root,
						  EquivalenceClass *fc_ec,
						  List *fc_exprs,
						  Relids fc_relids,
						  bool fc_require_parallel_safe)
{
	List	   *fc_exprvars;
	ListCell   *fc_lc;

	/*
	 * 从 "exprs" 中提取出 Vars 和准 Vars。通常情况下，非附加关系的情况只是同一列表的另一种表示。然而，它确实消除了普通表达式列表和 TargetEntry 列表之间的区别。
	 */
	fc_exprvars = pull_var_clause((Node *) fc_exprs,
							   PVC_INCLUDE_AGGREGATES |
							   PVC_INCLUDE_WINDOWFUNCS |
							   PVC_INCLUDE_PLACEHOLDERS);

	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc);
		List	   *fc_emvars;
		ListCell   *fc_lc2;

		/*
		 * 我们不应该尝试通过包含常量的等价类排序，
		 * 所以不需要进一步考虑这种情况。
		 */
		if (fc_em->em_is_const)
			continue;

		/*
		 * 除非它们属于请求的关系，否则忽略子成员。
		 */
		if (fc_em->em_is_child &&
			!bms_is_subset(fc_em->em_relids, fc_relids))
			continue;

		/*
		 * 如果 "exprs" 中存在所有 Vars 和准 Vars，则匹配。
		 */
		fc_emvars = pull_var_clause((Node *) fc_em->em_expr,
								 PVC_INCLUDE_AGGREGATES |
								 PVC_INCLUDE_WINDOWFUNCS |
								 PVC_INCLUDE_PLACEHOLDERS);
		foreach(fc_lc2, fc_emvars)
		{
			if (!list_member(fc_exprvars, lfirst(fc_lc2)))
				break;
		}
		list_free(fc_emvars);
		if (fc_lc2)
			continue;			/* 我们遇到了一个不可用的 Var */

		/*
		 * 如果请求，拒绝那些非并行安全的表达式。我们最后检查这一点，因为这是一个相当昂贵的测试。
		 */
		if (fc_require_parallel_safe &&
			!is_parallel_safe(fc_root, (Node *) fc_em->em_expr))
			continue;

		return fc_em;				/* 找到可用的表达式 */
	}

	return NULL;
}

/*
 * relation_can_be_sorted_early
 *		该关系能在最终输出步骤之前在这个 EC 上进行排序吗？
 *
 * 要成功，我们必须找到一个 EC 成员，prepare_sort_from_pathkeys 知道如何根据 rel 的 reltarget 进行排序。还存在一些附加约束，这基于希望的排序将在计划的扫描/连接部分“早期”完成。此外，如果 'require_parallel_safe'，则忽略非并行安全的表达式。
 *
 * 在某些时候，我们可能希望返回已识别的 EquivalenceMember，但目前，调用者只想知道是否存在一个。
 */
bool relation_can_be_sorted_early(PlannerInfo *fc_root, RelOptInfo *fc_rel,
							 EquivalenceClass *fc_ec, bool fc_require_parallel_safe)
{
	PathTarget *fc_target = fc_rel->reltarget;
	EquivalenceMember *fc_em;
	ListCell   *fc_lc;

	/*
	 * 立即拒绝波动的 EC；这样的排序必须始终延迟。
	 */
	if (fc_ec->ec_has_volatile)
		return false;

	/*
	 * 尝试直接找到匹配某个 reltarget 成员的 EM。
	 */
	foreach(fc_lc, fc_target->exprs)
	{
		Expr	   *fc_targetexpr = (Expr *) lfirst(fc_lc);

		fc_em = find_ec_member_matching_expr(fc_ec, fc_targetexpr, fc_rel->relids);
		if (!fc_em)
			continue;

		/*
		 * 拒绝涉及返回集合函数的表达式，因为这些也无法早期计算。（注意：此测试和后续测试实际上是在检查 targetexpr 的属性，因此询问是否某个其他 EC 成员会更好是没有意义的。）
		 */
		if (expression_returns_set((Node *) fc_em->em_expr))
			continue;

		/*
		 * 如果请求，拒绝那些非并行安全的表达式。我们最后检查这一点，因为这是一个相当昂贵的测试。
		 */
		if (fc_require_parallel_safe &&
			!is_parallel_safe(fc_root, (Node *) fc_em->em_expr))
			continue;

		return true;
	}

	/*
	 * 尝试找到可从 reltarget 计算的表达式。
	 */
	fc_em = find_computable_ec_member(fc_root, fc_ec, fc_target->exprs, fc_rel->relids,
								   fc_require_parallel_safe);
	if (!fc_em)
		return false;

	/*
	 * 拒绝涉及返回集合函数的表达式，因为这些也无法早期计算。（在这种情况下寻找另一个 EC 成员是没有意义的；因为 SRFs 不能出现在 WHERE 中，它们不能属于多成员 EC。）
	 */
	if (expression_returns_set((Node *) fc_em->em_expr))
		return false;

	return true;
}


/*
 * generate_base_implied_equalities
 *	  生成我们可以从等价类推断出的任何限制子句。
 *
 * 当一个EC包含伪常量时，我们的策略是为每个其他成员（包括其他常量）生成“member = const1”子句，其中const1是第一个常量成员。如果我们能够做到这一点，那么我们就不需要任何“var = var”的比较，因为我们已经成功地在变量创建时约束了所有变量。如果由于缺乏跨类型运算符而无法生成任何这些子句，我们将回退到下面描述的“ec_broken”策略。（XXX如果有多个不同类型的常量，从不同的常量成员开始，可能会成功形成所有所需的子句；但是，这似乎是一个足够奇怪的角落案例，值得花费大量周期去处理。）
 *
 * 对于不包含伪常量的EC，我们为属于同一基本关系的每对成员生成衍生子句“member1 = member2”（实际上，如果有多个属于同一基本关系的成员，我们只需要足够的子句来将每个成员链接在一起）。这为递归提供了基础案例：每个由基本关系扫描发出的行将约束EC的所有可计算成员相等。随着每个连接路径的形成，我们将动态添加额外的衍生子句以保持这一不变性（请参见generate_join_implied_equalities）。
 *
 * 如果EC使用的操作族不提供跨类型等式运算符的完整集合，那么我们可能会失败于生成必须生成以维持不变性的子句。（一个例子：给定“WHERE a.x = b.y AND b.y = a.z”，如果我们无法生成“a.x = a.z”作为A的限制子句，则计划会失败。）在这种情况下，我们将EC标记为“ec_broken”，并在适当的时候回退到原始的RestrictInfos。我们不会试图撤回已经从损坏的EC生成的任何衍生子句，因此，由于冗余子句导致的选择性估计不佳，结果计划可能会很糟糕。但解决这个问题的正确方法是修复操作族……
 *
 * 通过此函数推导出的等式子句将传递给process_implied_equality（在plan/initsplan.c中）以插入到restrictinfo数据结构中。请注意，这必须在对qual的初始扫描后并在路径构建开始之前调用。
 *
 * 我们没有尝试避免在此处生成重复的RestrictInfos：我们不搜索ec_sources或ec_derives以查找匹配项。这样做似乎不太值得麻烦。
 */
void generate_base_implied_equalities(PlannerInfo *fc_root)
{
	int			fc_ec_index;
	ListCell   *fc_lc;

	/*
	 * 此时，我们已经完成了对查询中等价关系的知识吸收，因此不应再发生EC合并，eq_classes列表中剩余的EC可以视为规范性。（但请注意，仍然可能通过get_eclass_for_sort_expr()添加新的单成员EC。）
	 */
	fc_root->ec_merging_done = true;

	fc_ec_index = 0;
	foreach(fc_lc, fc_root->eq_classes)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) lfirst(fc_lc);
		bool		fc_can_generate_joinclause = false;
		int			fc_i;

		Assert(fc_ec->ec_merged == NULL);	/* 否则不应在列表中 */
		Assert(!fc_ec->ec_broken); /* 反正还没呢…… */

		/*
		 * 生成作为限制子句的隐含等式。
		 * 单成员EC在此或连接级别都不会生成任何推理。
		 */
		if (list_length(fc_ec->ec_members) > 1)
		{
			if (fc_ec->ec_has_const)
				fc_generate_base_implied_equalities_const(fc_root, fc_ec);
			else
				fc_generate_base_implied_equalities_no_const(fc_root, fc_ec);

			/* 如果我们未能生成所需的衍生子句，请恢复 */
			if (fc_ec->ec_broken)
				fc_generate_base_implied_equalities_broken(fc_root, fc_ec);

			/* 检测此EC是否可能生成连接子句 */
			fc_can_generate_joinclause =
				(bms_membership(fc_ec->ec_relids) == BMS_MULTIPLE);
		}

		/*
		 * 标记在每个eclass中引用的基本关系（现在应该都存在）与提到它们的所有eclass的eq_classes索引。
		 * 这将使我们避免在后续查找中进行搜索。顺便说一下，我们可以标记具有待处理eclass连接的基本关系；
		 * 这是has_relevant_eclass_joinclause()的简化版本。
		 */
		fc_i = -1;
		while ((fc_i = bms_next_member(fc_ec->ec_relids, fc_i)) > 0)
		{
			RelOptInfo *fc_rel = fc_root->simple_rel_array[fc_i];

			Assert(fc_rel->reloptkind == RELOPT_BASEREL);

			fc_rel->eclass_indexes = bms_add_member(fc_rel->eclass_indexes,
												 fc_ec_index);

			if (fc_can_generate_joinclause)
				fc_rel->has_eclass_joins = true;
		}

		fc_ec_index++;
	}
}

/*
 * 当EC包含伪常量时生成base_implied_equalities
 */
static void fc_generate_base_implied_equalities_const(PlannerInfo *fc_root,
									   EquivalenceClass *fc_ec)
{
	EquivalenceMember *fc_const_em = NULL;
	ListCell   *fc_lc;

	/*
	 * 在只有一个“var = const”子句的简单情况下，将原始子句推回主规划器机械中。
	 * 这样做没有什么好处，我们也节省了重建和重新分析将与旧子句完全等效的等式子句的精力。
	 */
	if (list_length(fc_ec->ec_members) == 2 &&
		list_length(fc_ec->ec_sources) == 1)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) linitial(fc_ec->ec_sources);

		if (bms_membership(fc_restrictinfo->required_relids) != BMS_MULTIPLE)
		{
			distribute_restrictinfo_to_rels(fc_root, fc_restrictinfo);
			return;
		}
	}

	/*
	 * 找到要使用的常量成员。我们更倾向于实际常量而不是伪常量（例如Params），因为约束排除机制可能能够基于生成的“var = const”等式排除关系，而“var = param”则无法做到这一点。
	 */
	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc);

		if (fc_cur_em->em_is_const)
		{
			fc_const_em = fc_cur_em;
			if (IsA(fc_cur_em->em_expr, Const))
				break;
		}
	}
	Assert(fc_const_em != NULL);

	/* 针对每个其他成员生成衍生等式 */
	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc);
		Oid			fc_eq_op;
		RestrictInfo *fc_rinfo;

		Assert(!fc_cur_em->em_is_child);	/* 还没有子节点 */
		if (fc_cur_em == fc_const_em)
			continue;
		fc_eq_op = fc_select_equality_operator(fc_ec,
										 fc_cur_em->em_datatype,
										 fc_const_em->em_datatype);
		if (!OidIsValid(fc_eq_op))
		{
			/* 失败…… */
			fc_ec->ec_broken = true;
			break;
		}
		fc_rinfo = process_implied_equality(fc_root, fc_eq_op, fc_ec->ec_collation,
										 fc_cur_em->em_expr, fc_const_em->em_expr,
										 bms_copy(fc_ec->ec_relids),
										 bms_union(fc_cur_em->em_nullable_relids,
												   fc_const_em->em_nullable_relids),
										 fc_ec->ec_min_security,
										 fc_ec->ec_below_outer_join,
										 fc_cur_em->em_is_const);

		
/*
		 * 如果子句没有简化为常量，则填写合并可连接子句的正确标记，并将其保存在 ec_derives 中。 (我们不会直接重用这些子句，但选择性估计可能稍后会查阅该列表。 请注意，ec_derives 的这种使用与其对连接子句的使用并不重叠，因为我们从 ec_has_const eclass 生成连接子句。)
		 */
		if (fc_rinfo && fc_rinfo->mergeopfamilies)
		{
			/* 它不是冗余的，因此不要设置 parent_ec */
			fc_rinfo->left_ec = fc_rinfo->right_ec = fc_ec;
			fc_rinfo->left_em = fc_cur_em;
			fc_rinfo->right_em = fc_const_em;
			fc_ec->ec_derives = lappend(fc_ec->ec_derives, fc_rinfo);
		}
	}
}

/*
 * generate_base_implied_equalities 当 EC 不包含伪常数时
 */
static void fc_generate_base_implied_equalities_no_const(PlannerInfo *fc_root,
										  EquivalenceClass *fc_ec)
{
	EquivalenceMember **fc_prev_ems;
	ListCell   *fc_lc;

	/*
	 * 我们扫描 EC 成员一次，并跟踪每个基本关系的最后一个成员。
	 * 当我们看到同一基本关系的另一个成员时，
	 * 我们生成 "prev_em = cur_em"。这会导致产生最小数量的
	 * 派生子句，但可能会在不同的顺序可以成功的情况下失败。XXX 修复：使用类似于
	 * 我们构建合并 EC 的 UNION-FIND 算法。 （对每个关系使用一个列表的列表。）
	 */
	fc_prev_ems = (EquivalenceMember **)
		palloc0(fc_root->simple_rel_array_size * sizeof(EquivalenceMember *));

	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc);
		int			fc_relid;

		Assert(!fc_cur_em->em_is_child);	/* 还没有子节点 */
		if (!bms_get_singleton_member(fc_cur_em->em_relids, &fc_relid))
			continue;
		Assert(fc_relid < fc_root->simple_rel_array_size);

		if (fc_prev_ems[fc_relid] != NULL)
		{
			EquivalenceMember *fc_prev_em = fc_prev_ems[fc_relid];
			Oid			fc_eq_op;
			RestrictInfo *fc_rinfo;

			fc_eq_op = fc_select_equality_operator(fc_ec,
											 fc_prev_em->em_datatype,
											 fc_cur_em->em_datatype);
			if (!OidIsValid(fc_eq_op))
			{
				/* 失败…… */
				fc_ec->ec_broken = true;
				break;
			}
			fc_rinfo = process_implied_equality(fc_root, fc_eq_op, fc_ec->ec_collation,
											 fc_prev_em->em_expr, fc_cur_em->em_expr,
											 bms_copy(fc_ec->ec_relids),
											 bms_union(fc_prev_em->em_nullable_relids,
													   fc_cur_em->em_nullable_relids),
											 fc_ec->ec_min_security,
											 fc_ec->ec_below_outer_join,
											 false);

			/*
			 * 如果子句没有退化为常量，填写合并可连接子句的
			 * 正确标记。然而，我们不将其放入 ec_derives；我们当前
			 * 不需要重新查找这样的子句，并且我们不希望用非连接
			 * 子句来淹没该列表。
			 */
			if (fc_rinfo && fc_rinfo->mergeopfamilies)
			{
				/* 它不是冗余的，因此不要设置 parent_ec */
				fc_rinfo->left_ec = fc_rinfo->right_ec = fc_ec;
				fc_rinfo->left_em = fc_prev_em;
				fc_rinfo->right_em = fc_cur_em;
			}
		}
		fc_prev_ems[fc_relid] = fc_cur_em;
	}

	pfree(fc_prev_ems);

	/*
	 * 我们还必须确保在成员子句中使用的所有变量
	 * 在我们可能尝试引用它们的任何连接节点都可用。
	 * 目前我们强制所有变量在该 eclass 的所有
	 * 连接节点上可用。也许可以通过对我们希望连接的成员
	 * 进行一些预分析来改善，但与预 8.3 代码中发生的情况
	 * 相比并没有更糟。
	 */
	foreach(fc_lc, fc_ec->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc);
		List	   *fc_vars = pull_var_clause((Node *) fc_cur_em->em_expr,
										   PVC_RECURSE_AGGREGATES |
										   PVC_RECURSE_WINDOWFUNCS |
										   PVC_INCLUDE_PLACEHOLDERS);

		add_vars_to_targetlist(fc_root, fc_vars, fc_ec->ec_relids, false);
		list_free(fc_vars);
	}
}

/*
 * generate_base_implied_equalities 失败后的清理
 *
 * 我们在这里必须将 EC 的任何零或一关系源的 RestrictInfos 
 * 推回主 restrictinfo 数据结构中。多关系子句将在稍后由 
 * generate_join_implied_equalities() 再次处理。
 * （我们这样做是为了与只有在我们上升
 * 一到两个连接级别时 ec_broken 才被设置的情况保持一致。）
 * 然而，对于包含常量的 EC，我们可以采用一个更简单的策略，
 * 立即返回所有源 RestrictInfos；这有效是因为
 * 我们知道这样的 EC 在后面不会破裂。 （这个规则证明了
 * 在 generate_join_implied_equalities 中忽略 ec_has_const ECs，
 * 即使它们是破裂的。）
 */
static void fc_generate_base_implied_equalities_broken(PlannerInfo *fc_root,
										EquivalenceClass *fc_ec)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_ec->ec_sources)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) lfirst(fc_lc);

		if (fc_ec->ec_has_const ||
			bms_membership(fc_restrictinfo->required_relids) != BMS_MULTIPLE)
			distribute_restrictinfo_to_rels(fc_root, fc_restrictinfo);
	}
}


/*
 * generate_join_implied_equalities
 *	  生成我们可以从等价类推导出的任何连接子句。
 *
 * 在连接节点上，我们必须执行限制子句，以确保
 * 在该节点计算的所有等价类成员都是相等的。
 * 由于需要强制实施的子句集可能会根据输入的
 * 子集关系而变化，因此我们必须为每对连接关系重新计算此值。
 * 因此，将构建一个新的 RestrictInfo 节点列表，
 * 并在每次调用时传回。
 *
 * 除了在连接节点上的使用外，这也可以用于生成
 * 基于 eclass 的连接子句，以便在基本关系的参数化扫描中使用。
 * 指定内部关系为 RelOptInfo 和外部关系为 Relids 的不对称性
 * 是因为此用法发生在我们构建任何连接 RelOptInfos 之前。
 *
 * 参数化扫描的一个烦人的特例是，内部关系可以
 * 是附加关系子项（“其他关系”）。在这种情况下，我们必须使用
 * 子 EC 成员生成适当的子句。必须已经
 * 对子关系执行 add_child_rel_equivalences。
 *
 * 结果足以在合并、哈希和平普通嵌套循环连接方法中使用。
 * 我们在这里不担心选择在参数化索引扫描中使用的最佳子句。
 * indxpath.c 自行选择要使用的子句，如果我们在这里选择的子句
 * 与那些子句冗余，则在创建计划时会使用我们提供的父 ec 
 * 标记（参见 is_redundant_derived_clause()）消除额外部分。
 *
 * 因为考虑不同连接路径时可能需要多次使用相同的连接子句，
 * 我们避免生成多个副本：
 * 每当我们选择一对特定的 EquivalenceMembers 进行连接时，
 * 我们检查该对是否匹配任何原始子句（在 ec_sources 中）
 * 或以前构建的子句（在 ec_derives 中）。这节省了内存并允许
 * 重用缓存的 RestrictInfos 中的信息。
 *
 * join_relids 应始终等于 bms_union(outer_relids, inner_rel->relids)。
 * 我们可以通过在内部计算来简化此函数的 API，但在
 * 当前大多数使用中，调用者手中都有该值。
 */
List * generate_join_implied_equalities(PlannerInfo *fc_root,
								 Relids fc_join_relids,
								 Relids fc_outer_relids,
								 RelOptInfo *fc_inner_rel)
{
	List	   *fc_result = NIL;
	Relids		fc_inner_relids = fc_inner_rel->relids;
	Relids		fc_nominal_inner_relids;
	Relids		fc_nominal_join_relids;
	Bitmapset  *fc_matching_ecs;
	int			fc_i;

	/* 如果内部关系是子关系，则需要额外的设置工作 */
	if (IS_OTHER_REL(fc_inner_rel))
	{
		Assert(!bms_is_empty(fc_inner_rel->top_parent_relids));

		/* 获取最上层父关系的 relid 集 */
		fc_nominal_inner_relids = fc_inner_rel->top_parent_relids;
		/* EC 将使用父关系的 relid 标记，而不是子关系的 */
		fc_nominal_join_relids = bms_union(fc_outer_relids, fc_nominal_inner_relids);
	}
	else
	{
		fc_nominal_inner_relids = fc_inner_relids;
		fc_nominal_join_relids = fc_join_relids;
	}

	/*
	 * 获取提及连接的内部和外部侧的所有 eclasses
	 */
	fc_matching_ecs = get_common_eclass_indexes(fc_root, fc_nominal_inner_relids,
											 fc_outer_relids);

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_matching_ecs, fc_i)) >= 0)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes, fc_i);
		List	   *fc_sublist = NIL;

		/* 包含常量的 EC 不需要任何进一步的强制执行 */
		if (fc_ec->ec_has_const)
			continue;

		/* 单成员 EC 不会生成任何推导 */
		if (list_length(fc_ec->ec_members) <= 1)
			continue;

		/* 确认该 eclass 与连接重叠 */
		Assert(bms_overlap(fc_ec->ec_relids, fc_nominal_join_relids));

		if (!fc_ec->ec_broken)
			fc_sublist = fc_generate_join_implied_equalities_normal(fc_root,
															  fc_ec,
															  fc_join_relids,
															  fc_outer_relids,
															  fc_inner_relids);

		/* 如果我们未能生成所需的衍生子句，请恢复 */
		if (fc_ec->ec_broken)
			fc_sublist = fc_generate_join_implied_equalities_broken(fc_root,
															  fc_ec,
															  fc_nominal_join_relids,
															  fc_outer_relids,
															  fc_nominal_inner_relids,
															  fc_inner_rel);

		fc_result = list_concat(fc_result, fc_sublist);
	}

	return fc_result;
}

/*
 * generate_join_implied_equalities_for_ecs
 *	  如上，但仅考虑列出的 EC。
 */
List * generate_join_implied_equalities_for_ecs(PlannerInfo *fc_root,
										 List *fc_eclasses,
										 Relids fc_join_relids,
										 Relids fc_outer_relids,
										 RelOptInfo *fc_inner_rel)
{
	List	   *fc_result = NIL;
	Relids		fc_inner_relids = fc_inner_rel->relids;
	Relids		fc_nominal_inner_relids;
	Relids		fc_nominal_join_relids;
	ListCell   *fc_lc;

	/* 如果内部关系是子关系，则需要额外的设置工作 */
	if (IS_OTHER_REL(fc_inner_rel))
	{
		Assert(!bms_is_empty(fc_inner_rel->top_parent_relids));

		/* 获取最上层父关系的 relid 集 */
		fc_nominal_inner_relids = fc_inner_rel->top_parent_relids;
		/* EC 将使用父关系的 relid 标记，而不是子关系的 */
		fc_nominal_join_relids = bms_union(fc_outer_relids, fc_nominal_inner_relids);
	}
	else
	{
		fc_nominal_inner_relids = fc_inner_relids;
		fc_nominal_join_relids = fc_join_relids;
	}

	foreach(fc_lc, fc_eclasses)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) lfirst(fc_lc);
		List	   *fc_sublist = NIL;

		/* 包含常量的 EC 不需要任何进一步的强制执行 */
		if (fc_ec->ec_has_const)
			continue;

		/* 单成员 EC 不会生成任何推导 */
		if (list_length(fc_ec->ec_members) <= 1)
			continue;

		/* 我们也可以快速忽略任何不重叠连接的 EC */
		if (!bms_overlap(fc_ec->ec_relids, fc_nominal_join_relids))
			continue;

		if (!fc_ec->ec_broken)
			fc_sublist = fc_generate_join_implied_equalities_normal(fc_root,
															  fc_ec,
															  fc_join_relids,
															  fc_outer_relids,
															  fc_inner_relids);

		/* 如果我们未能生成所需的衍生子句，请恢复 */
		if (fc_ec->ec_broken)
			fc_sublist = fc_generate_join_implied_equalities_broken(fc_root,
															  fc_ec,
															  fc_nominal_join_relids,
															  fc_outer_relids,
															  fc_nominal_inner_relids,
															  fc_inner_rel);

		fc_result = list_concat(fc_result, fc_sublist);
	}

	return fc_result;
}


/*
 * generate_join_implied_equalities for a still-valid EC
 */
static List * fc_generate_join_implied_equalities_normal(PlannerInfo *fc_root,
										EquivalenceClass *fc_ec,
										Relids fc_join_relids,
										Relids fc_outer_relids,
										Relids fc_inner_relids)
{
	List	   *fc_result = NIL;
	List	   *fc_new_members = NIL;
	List	   *fc_outer_members = NIL;
	List	   *fc_inner_members = NIL;
	ListCell   *fc_lc1;

	/*
	 * 首先，扫描 EC 以识别在外部关系、内部关系或该关系中但不在任一输入关系中的可计算成员值。外部关系的成员应该已经被强制相等，内部关系的成员也是如此。我们需要创建子句以强制所有新可计算成员彼此相等，以及至少与一个输入成员相等，并强制至少一个外部关系成员等于至少一个内部关系成员。
	 */
	foreach(fc_lc1, fc_ec->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc1);

		/*
		 * 我们不需要显式检查子 EC 成员。这个针对 join_relids 的测试将导致它们被忽略，除非在考虑子内部关系时，这正是我们所需要的。
		 */
		if (!bms_is_subset(fc_cur_em->em_relids, fc_join_relids))
			continue;			/* 还不能计算，或者是错误的子关系 */

		if (bms_is_subset(fc_cur_em->em_relids, fc_outer_relids))
			fc_outer_members = lappend(fc_outer_members, fc_cur_em);
		else if (bms_is_subset(fc_cur_em->em_relids, fc_inner_relids))
			fc_inner_members = lappend(fc_inner_members, fc_cur_em);
		else
			fc_new_members = lappend(fc_new_members, fc_cur_em);
	}

	/*
	 * 首先，如果需要，选择 joinclause。我们可以将任何一个外部成员与任何一个内部成员等同，但我们必须找到一个存在运算符家族成员运算符的数据类型组合。如果我们有选择，我们偏好简单的 Var 成员（可能带有 RelabelType），因为这些在运行时计算成本 (a) 最低，(b) 最可能具有有用的统计信息。此外，偏好那些也可以进行哈希连接的运算符。
	 */
	if (fc_outer_members && fc_inner_members)
	{
		EquivalenceMember *fc_best_outer_em = NULL;
		EquivalenceMember *fc_best_inner_em = NULL;
		Oid			fc_best_eq_op = InvalidOid;
		int			fc_best_score = -1;
		RestrictInfo *fc_rinfo;

		foreach(fc_lc1, fc_outer_members)
		{
			EquivalenceMember *fc_outer_em = (EquivalenceMember *) lfirst(fc_lc1);
			ListCell   *fc_lc2;

			foreach(fc_lc2, fc_inner_members)
			{
				EquivalenceMember *fc_inner_em = (EquivalenceMember *) lfirst(fc_lc2);
				Oid			fc_eq_op;
				int			fc_score;

				fc_eq_op = fc_select_equality_operator(fc_ec,
												 fc_outer_em->em_datatype,
												 fc_inner_em->em_datatype);
				if (!OidIsValid(fc_eq_op))
					continue;
				fc_score = 0;
				if (IsA(fc_outer_em->em_expr, Var) ||
					(IsA(fc_outer_em->em_expr, RelabelType) &&
					 IsA(((RelabelType *) fc_outer_em->em_expr)->arg, Var)))
					fc_score++;
				if (IsA(fc_inner_em->em_expr, Var) ||
					(IsA(fc_inner_em->em_expr, RelabelType) &&
					 IsA(((RelabelType *) fc_inner_em->em_expr)->arg, Var)))
					fc_score++;
				if (op_hashjoinable(fc_eq_op,
									exprType((Node *) fc_outer_em->em_expr)))
					fc_score++;
				if (fc_score > fc_best_score)
				{
					fc_best_outer_em = fc_outer_em;
					fc_best_inner_em = fc_inner_em;
					fc_best_eq_op = fc_eq_op;
					fc_best_score = fc_score;
					if (fc_best_score == 3)
						break;	/* 没必要再进一步查看 */
				}
			}
			if (fc_best_score == 3)
				break;			/* 没必要再进一步查看 */
		}
		if (fc_best_score < 0)
		{
			/* 失败…… */
			fc_ec->ec_broken = true;
			return NIL;
		}

		/*
		 * 创建子句，设置 parent_ec 以标记其与其他 joinclauses 的冗余
		 */
		fc_rinfo = fc_create_join_clause(fc_root, fc_ec, fc_best_eq_op,
								   fc_best_outer_em, fc_best_inner_em,
								   fc_ec);

		fc_result = lappend(fc_result, fc_rinfo);
	}

	/*
	 * 现在处理涉及来自连接两侧的 Vars 的任何表达式的构建限制。我们必须将所有这些成员彼此等同，并且至少与一个旧成员（如果有的话）等同。
	 *
	 * XXX 在 generate_base_implied_equalities_no_const 中，我们可以在这里变得更聪明，以避免在跨类型情况下不必要的失败。现在，使用之前相同的从左到右的方法。
	 */
	if (fc_new_members)
	{
		List	   *fc_old_members = list_concat(fc_outer_members, fc_inner_members);
		EquivalenceMember *fc_prev_em = NULL;
		RestrictInfo *fc_rinfo;

		/* 目前，任意取第一个旧成员作为要使用的成员 */
		if (fc_old_members)
			fc_new_members = lappend(fc_new_members, linitial(fc_old_members));

		foreach(fc_lc1, fc_new_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc1);

			if (fc_prev_em != NULL)
			{
				Oid			fc_eq_op;

				fc_eq_op = fc_select_equality_operator(fc_ec,
												 fc_prev_em->em_datatype,
												 fc_cur_em->em_datatype);
				if (!OidIsValid(fc_eq_op))
				{
					/* 失败…… */
					fc_ec->ec_broken = true;
					return NIL;
				}
				/* 不要设置 parent_ec，这个质量不是冗余的！ */
				fc_rinfo = fc_create_join_clause(fc_root, fc_ec, fc_eq_op,
										   fc_prev_em, fc_cur_em,
										   NULL);

				fc_result = lappend(fc_result, fc_rinfo);
			}
			fc_prev_em = fc_cur_em;
		}
	}

	return fc_result;
}

/*
 * generate_join_implied_equalities cleanup after failure
 *
 * 返回在此连接上可强制实施的任何原始 RestrictInfos。
 *
 * 在子内部关系的情况下，我们必须将原始 RestrictInfos 从父级转换为子级 Vars。
 */
static List * fc_generate_join_implied_equalities_broken(PlannerInfo *fc_root,
										EquivalenceClass *fc_ec,
										Relids fc_nominal_join_relids,
										Relids fc_outer_relids,
										Relids fc_nominal_inner_relids,
										RelOptInfo *fc_inner_rel)
{
	List	   *fc_result = NIL;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_ec->ec_sources)
	{
		RestrictInfo *fc_restrictinfo = (RestrictInfo *) lfirst(fc_lc);
		Relids		fc_clause_relids = fc_restrictinfo->required_relids;

		if (bms_is_subset(fc_clause_relids, fc_nominal_join_relids) &&
			!bms_is_subset(fc_clause_relids, fc_outer_relids) &&
			!bms_is_subset(fc_clause_relids, fc_nominal_inner_relids))
			fc_result = lappend(fc_result, fc_restrictinfo);
	}

	/*
	 * 如果我们必须转换，只需对所有 RestrictInfos 同时强制应用 adjust_appendrel_attrs。这将导致返回未列入 ec_derives 的 RestrictInfos，但不应该有任何重复，并且这是一个足够狭窄的特殊情况，因此我们不必太担心。
	 *
	 * 由于 inner_rel 可能是提到的 ec_sources 子句中 baserel 的间接后代，我们必须准备应用多个层次的 Var 转换。
	 */
	if (IS_OTHER_REL(fc_inner_rel) && fc_result != NIL)
		fc_result = (List *) adjust_appendrel_attrs_multilevel(fc_root,
															(Node *) fc_result,
															fc_inner_rel->relids,
															fc_inner_rel->top_parent_relids);

	return fc_result;
}


/*
 * select_equality_operator
 *	  选择一个合适的相等运算符，以比较两个 EC 成员
 *
 * 如果找不到用于此数据类型组合的运算符，则返回 InvalidOid
 */
static Oid fc_select_equality_operator(EquivalenceClass *fc_ec, Oid fc_lefttype, Oid fc_righttype)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_ec->ec_opfamilies)
	{
		Oid			fc_opfamily = lfirst_oid(fc_lc);
		Oid			fc_opno;

		fc_opno = get_opfamily_member(fc_opfamily, fc_lefttype, fc_righttype,
								   BTEqualStrategyNumber);
		if (!OidIsValid(fc_opno))
			continue;
		/* 如果查询中没有障碍限定条件，则不必担心泄漏运算符 */
		if (fc_ec->ec_max_security == 0)
			return fc_opno;
		/* 否则，要求所选运算符是防泄漏的 */
		if (get_func_leakproof(get_opcode(fc_opno)))
			return fc_opno;
	}
	return InvalidOid;
}


/*
 * create_join_clause
 *	  查找或创建一个 RestrictInfo，以比较给定的两个 EC 成员
 *	  与给定的运算符。
 *
 * parent_ec 要么等于 ec（如果子句是一个潜在的冗余
 * 连接子句），要么为 NULL（如果不是）。我们必须将其视为匹配要求的一部分——比较同两个 EM 的子句可能在一个连接路径中是连接子句，而在另一个连接路径中是限制子句。
 */
static RestrictInfo * fc_create_join_clause(PlannerInfo *fc_root,
				   EquivalenceClass *fc_ec, Oid fc_opno,
				   EquivalenceMember *fc_leftem,
				   EquivalenceMember *fc_rightem,
				   EquivalenceClass *fc_parent_ec)
{
	RestrictInfo *fc_rinfo;
	ListCell   *fc_lc;
	MemoryContext fc_oldcontext;

	/*
	 * 搜索以查看我们是否已经为这一对等价成员构建了 RestrictInfo。
	 * 我们可以使用原始源子句或先前派生的子句。对 opno 的检查可能是冗余的，
	 * 但还是要小心……
	 */
	foreach(fc_lc, fc_ec->ec_sources)
	{
		fc_rinfo = (RestrictInfo *) lfirst(fc_lc);
		if (fc_rinfo->left_em == fc_leftem &&
			fc_rinfo->right_em == fc_rightem &&
			fc_rinfo->parent_ec == fc_parent_ec &&
			fc_opno == ((OpExpr *) fc_rinfo->clause)->opno)
			return fc_rinfo;
	}

	foreach(fc_lc, fc_ec->ec_derives)
	{
		fc_rinfo = (RestrictInfo *) lfirst(fc_lc);
		if (fc_rinfo->left_em == fc_leftem &&
			fc_rinfo->right_em == fc_rightem &&
			fc_rinfo->parent_ec == fc_parent_ec &&
			fc_opno == ((OpExpr *) fc_rinfo->clause)->opno)
			return fc_rinfo;
	}

	/*
	 * 不在那儿，所以构建它，在规划上下文中以便我们可以重复使用它。（在正常规划中不重要，
	 * 但在 GEQO 中绝对重要。）
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_root->planner_cxt);

	fc_rinfo = build_implied_join_equality(fc_root,
										fc_opno,
										fc_ec->ec_collation,
										fc_leftem->em_expr,
										fc_rightem->em_expr,
										bms_union(fc_leftem->em_relids,
												  fc_rightem->em_relids),
										bms_union(fc_leftem->em_nullable_relids,
												  fc_rightem->em_nullable_relids),
										fc_ec->ec_min_security);

	/*
	 * 如果任一 EM 是子项，则强制子句的 clause_relids 包含
	 * 子关系的 relid(s)。在正常情况下，它本来就会包含，但在
	 * 我们考虑带有伪恒定翻译变量的 appendrel 子关系时（即包含常量输出
	 * 项的 UNION ALL 子选项），就不一定了。我们必须这样做，以便 join_clause_is_movable_into() 会
	 * 认为该子句应在正确的地方进行评估。
	 */
	if (fc_leftem->em_is_child)
		fc_rinfo->clause_relids = bms_add_members(fc_rinfo->clause_relids,
											   fc_leftem->em_relids);
	if (fc_rightem->em_is_child)
		fc_rinfo->clause_relids = bms_add_members(fc_rinfo->clause_relids,
											   fc_rightem->em_relids);

	/* 将子句标记为冗余，或不冗余 */
	fc_rinfo->parent_ec = fc_parent_ec;

	/*
	 * 我们知道 left_ec/right_ec 的正确值，即这个特定的 EC，
	 * 因此我们可以直接设置它们，而不必强制进行另一次查找。
	 */
	fc_rinfo->left_ec = fc_ec;
	fc_rinfo->right_ec = fc_ec;

	/* 将其标记为可与这些 EM 一起使用 */
	fc_rinfo->left_em = fc_leftem;
	fc_rinfo->right_em = fc_rightem;
	/* 并保存以备可能的重复使用 */
	fc_ec->ec_derives = lappend(fc_ec->ec_derives, fc_rinfo);

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_rinfo;
}



/*
 * reconsider_outer_join_clauses
 *	  重新审视任何被 distribute_qual_to_rels() 搁置的外连接子句，并查看我们是否可以从中推导出任何
 *	  EquivalenceClasses。然后，如果它们没有被冗余化，就将它们推入常规的连接子句列表中。
 *
 * 当我们有可合并的子句 A = B 作为外连接子句时，
 * 我们不能盲目地将它们与其他子句 A = C 组合以推断 B = C，
 * 因为事实上，“等式” A = B 在外连接上不一定成立（其中一个变量可能是 NULL）。
 * 尽管如此，有些情况下我们可以利用传递性添加质子句。
 *
 * 我们在这里寻找的一个情况是外连接子句 OUTERVAR = INNERVAR
 *，并且还有一个等价子句 OUTERVAR = CONSTANT。
 * 推送子句 INNERVAR = CONSTANT 进入内部（可空）关系的评估是安全和有用的，
 * 因为任何不满足此条件的内部行反正不会对外连接结果产生贡献。
 * （它们可以连接的任何外部行将通过已推送的等价子句被消除。）
 *
 * 请注意，上述规则不适用于完全外连接；也不太有趣考虑生成的等价子句
 * 涉及外连接之外的关系，因为此类子句反正无法推入内部侧的扫描。
 * 所以对 outervar = pseudoconstant 的限制并没有真正放弃什么。
 *
 * 对于完全连接情况，我们只能在使用 FULL JOIN 时
 * 做一些有用的事情，并且合并列有一个等价 MERGEDVAR = CONSTANT。
 * 到这里时，合并列看起来像
 *		COALESCE(LEFTVAR, RIGHTVAR)
 * 并且我们将有一个完全连接子句 LEFTVAR = RIGHTVAR 可以与
 * COALESCE 表达式匹配。在这种情况下，我们可以将 LEFTVAR = CONSTANT
 * 和 RIGHTVAR = CONSTANT 推入输入关系中，因为不满足这些条件的行将无法对连接结果做出贡献。
 *
 * 再次强调，试图处理将 mergedvar 与非伪常量进行比较的子句没有任何进展。
 * 因此，我们可以利用 EquivalenceClasses 搜索与常量等价的匹配变量。
 * 有趣的外连接子句已经由 distribute_qual_to_rels 为我们累积。
 *
 * 当我们找到这些情况之一时，我们通过生成一个新的等价子句 INNERVAR = CONSTANT（或 LEFTVAR 等）
 * 并将其推入 EquivalenceClass 结构来实施我们想要的更改。这是因为我们可能已经知道 INNERVAR
 * 与其他变量等价，并且我们希望常量也能传播到它们。请注意，将任何现有的 INNERVAR 的 EC 与
 * OUTERVAR 的 EC 合并是不安全的——这可能会导致将常量限制从 INNERVAR 传播到 OUTERVAR，这将是非常错误的。
 *
 * 有可能 INNERVAR 也是某个其他外连接子句的 OUTERVAR，在这种情况下，可以重复该过程。
 * 因此，我们重复循环遍历子句列表，直到无法再进行进一步的推导为止。
 * 每当我们进行推导时，我们会从列表中删除生成子句，因为我们不想重复进行相同的推导。
 *
 * 如果我们没有找到搁置的外连接子句的匹配项，我们必须将其重新放回常规的连接子句处理过程，
 * 通过将其传递给 distribute_restrictinfo_to_rels()。然而，如果我们生成了derive 子句，
 * 外连接子句就是冗余的。我们仍然将其抛回，
 * 因为否则连接将被视为无子句连接，并在连接顺序搜索中被避免；但我们将其标记为冗余，以避免搞乱 connectrel 的大小估算。
 * （这种行为意味着该例程的 API 是无用复杂的：我们本可以在最初将所有子句都放入常规处理。
 * 我们保留它，因为将来我们可能想做其他事情，例如插入“虚拟”连接子句而不是实际的子句。）
 *
 * 标记为 outerjoin_delayed 的外连接子句是特殊的：此条件意味着由于较低的外连接，
 * 一个或两个 VAR 可能会变为 NULL。我们仍然可以通过子句推送常量，但只有在其操作符是严格的情况下；并且我们 *必须*
 * 将子句抛回常规连接子句处理。通过保留严格的连接子句，
 * 我们确保由于抑制不匹配常量的行而错误生成的任何 NULL 扩展行将在上层外连接处被拒绝。
 * （这不适用于完全连接子句。）
 */
void reconsider_outer_join_clauses(PlannerInfo *fc_root)
{
	bool		fc_found;
	ListCell   *fc_cell;

	/* 外循环重复，直到我们找不到更多推导 */
	do
	{
		fc_found = false;

		/* 处理 LEFT JOIN 子句 */
		foreach(fc_cell, fc_root->left_join_clauses)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

			if (fc_reconsider_outer_join_clause(fc_root, fc_rinfo, true))
			{
				fc_found = true;
				/* 从列表中移除它 */
				fc_root->left_join_clauses =
					foreach_delete_current(fc_root->left_join_clauses, fc_cell);
				/* 我们还是将其抛回（详见上面的说明） */
				/* 但是被抛回的子句没有额外的选择性 */
				fc_rinfo->norm_selec = 2.0;
				fc_rinfo->outer_selec = 1.0;
				distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
			}
		}

		
/* 处理 RIGHT JOIN 子句 */
		foreach(fc_cell, fc_root->right_join_clauses)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

			if (fc_reconsider_outer_join_clause(fc_root, fc_rinfo, false))
			{
				fc_found = true;
				/* 从列表中移除它 */
				fc_root->right_join_clauses =
					foreach_delete_current(fc_root->right_join_clauses, fc_cell);
				/* 我们还是将其抛回（详见上面的说明） */
				/* 但是被抛回的子句没有额外的选择性 */
				fc_rinfo->norm_selec = 2.0;
				fc_rinfo->outer_selec = 1.0;
				distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
			}
		}

		/* 处理 FULL JOIN 子句 */
		foreach(fc_cell, fc_root->full_join_clauses)
		{
			RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

			if (fc_reconsider_full_join_clause(fc_root, fc_rinfo))
			{
				fc_found = true;
				/* 从列表中移除它 */
				fc_root->full_join_clauses =
					foreach_delete_current(fc_root->full_join_clauses, fc_cell);
				/* 我们还是将其抛回（详见上面的说明） */
				/* 但是被抛回的子句没有额外的选择性 */
				fc_rinfo->norm_selec = 2.0;
				fc_rinfo->outer_selec = 1.0;
				distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
			}
		}
	} while (fc_found);

	/* 现在，任何剩余的子句必须被抛回 */
	foreach(fc_cell, fc_root->left_join_clauses)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

		distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
	}
	foreach(fc_cell, fc_root->right_join_clauses)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

		distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
	}
	foreach(fc_cell, fc_root->full_join_clauses)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_cell);

		distribute_restrictinfo_to_rels(fc_root, fc_rinfo);
	}
}

/*
 * reconsider_outer_join_clauses 用于单个 LEFT/RIGHT JOIN 子句
 *
 * 如果我们能够将常量传播通过子句，则返回 true。
 */
static bool fc_reconsider_outer_join_clause(PlannerInfo *fc_root, RestrictInfo *fc_rinfo,
							 bool fc_outer_on_left)
{
	Expr	   *fc_outervar,
			   *fc_innervar;
	Oid			fc_opno,
				fc_collation,
				fc_left_type,
				fc_right_type,
				fc_inner_datatype;
	Relids		fc_inner_relids,
				fc_inner_nullable_relids;
	ListCell   *fc_lc1;

	Assert(is_opclause(fc_rinfo->clause));
	fc_opno = ((OpExpr *) fc_rinfo->clause)->opno;
	fc_collation = ((OpExpr *) fc_rinfo->clause)->inputcollid;

	/* 如果子句是 outerjoin_delayed，操作符必须是严格的 */
	if (fc_rinfo->outerjoin_delayed && !op_strict(fc_opno))
		return false;

	/* 从子句中提取所需的信息 */
	op_input_types(fc_opno, &fc_left_type, &fc_right_type);
	if (fc_outer_on_left)
	{
		fc_outervar = (Expr *) get_leftop(fc_rinfo->clause);
		fc_innervar = (Expr *) get_rightop(fc_rinfo->clause);
		fc_inner_datatype = fc_right_type;
		fc_inner_relids = fc_rinfo->right_relids;
	}
	else
	{
		fc_outervar = (Expr *) get_rightop(fc_rinfo->clause);
		fc_innervar = (Expr *) get_leftop(fc_rinfo->clause);
		fc_inner_datatype = fc_left_type;
		fc_inner_relids = fc_rinfo->left_relids;
	}
	fc_inner_nullable_relids = bms_intersect(fc_inner_relids,
										  fc_rinfo->nullable_relids);

	/* 扫描 EquivalenceClasses 以匹配 outervar */
	foreach(fc_lc1, fc_root->eq_classes)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) lfirst(fc_lc1);
		bool		fc_match;
		ListCell   *fc_lc2;

		/* 忽略 EC，除非它包含伪常量 */
		if (!fc_cur_ec->ec_has_const)
			continue;
		/* 永远不要匹配到易变的 EC */
		if (fc_cur_ec->ec_has_volatile)
			continue;
		/* 它也必须在语义上与外连接子句相匹配 */
		if (fc_collation != fc_cur_ec->ec_collation)
			continue;
		if (!equal(fc_rinfo->mergeopfamilies, fc_cur_ec->ec_opfamilies))
			continue;
		/* 它是否包含与 outervar 的匹配？ */
		fc_match = false;
		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc2);

			Assert(!fc_cur_em->em_is_child);	/* 还没有子节点 */
			if (equal(fc_outervar, fc_cur_em->em_expr))
			{
				fc_match = true;
				break;
			}
		}
		if (!fc_match)
			continue;			/* 无匹配，因此忽略此 EC */

		/*
		 * 是的，它包含！ 尝试生成一个子句 INNERVAR = CONSTANT 对于 EC 中的每个
		 * CONSTANT。 请注意，我们必须至少成功使用一个
		 * 常量，然后才能决定丢弃外连接子句。
		 */
		fc_match = false;
		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc2);
			Oid			fc_eq_op;
			RestrictInfo *fc_newrinfo;

			if (!fc_cur_em->em_is_const)
				continue;		/* 忽略非常量成员 */
			fc_eq_op = fc_select_equality_operator(fc_cur_ec,
											 fc_inner_datatype,
											 fc_cur_em->em_datatype);
			if (!OidIsValid(fc_eq_op))
				continue;		/* 无法生成相等关系 */
			fc_newrinfo = build_implied_join_equality(fc_root,
												   fc_eq_op,
												   fc_cur_ec->ec_collation,
												   fc_innervar,
												   fc_cur_em->em_expr,
												   bms_copy(fc_inner_relids),
												   bms_copy(fc_inner_nullable_relids),
												   fc_cur_ec->ec_min_security);
			if (process_equivalence(fc_root, &fc_newrinfo, true))
				fc_match = true;
		}

		/*
		 * 如果我们能够将 INNERVAR 等同于任何常量，则报告成功。
		 * 否则，退出搜索循环，因为我们知道 OUTERVAR
		 * 至多出现在一个 EC 中。
		 */
		if (fc_match)
			return true;
		else
			break;
	}

	return false;				/* 未能做出任何推导 */
}

/*
 * reconsider_outer_join_clauses 针对单个 FULL JOIN 子句
 *
 * 如果我们能够在子句中传播常量，则返回 true。
 */
static bool fc_reconsider_full_join_clause(PlannerInfo *fc_root, RestrictInfo *fc_rinfo)
{
	Expr	   *fc_leftvar;
	Expr	   *fc_rightvar;
	Oid			fc_opno,
				fc_collation,
				fc_left_type,
				fc_right_type;
	Relids		fc_left_relids,
				fc_right_relids,
				fc_left_nullable_relids,
				fc_right_nullable_relids;
	ListCell   *fc_lc1;

	/* 这里不能使用 outerjoin_delayed 子句 */
	if (fc_rinfo->outerjoin_delayed)
		return false;

	/* 从子句中提取所需的信息 */
	Assert(is_opclause(fc_rinfo->clause));
	fc_opno = ((OpExpr *) fc_rinfo->clause)->opno;
	fc_collation = ((OpExpr *) fc_rinfo->clause)->inputcollid;
	op_input_types(fc_opno, &fc_left_type, &fc_right_type);
	fc_leftvar = (Expr *) get_leftop(fc_rinfo->clause);
	fc_rightvar = (Expr *) get_rightop(fc_rinfo->clause);
	fc_left_relids = fc_rinfo->left_relids;
	fc_right_relids = fc_rinfo->right_relids;
	fc_left_nullable_relids = bms_intersect(fc_left_relids,
										 fc_rinfo->nullable_relids);
	fc_right_nullable_relids = bms_intersect(fc_right_relids,
										  fc_rinfo->nullable_relids);

	foreach(fc_lc1, fc_root->eq_classes)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) lfirst(fc_lc1);
		EquivalenceMember *fc_coal_em = NULL;
		bool		fc_match;
		bool		fc_matchleft;
		bool		fc_matchright;
		ListCell   *lc2;
		int			fc_coal_idx = -1;

		/* 忽略 EC，除非它包含伪常量 */
		if (!fc_cur_ec->ec_has_const)
			continue;
		/* 永远不要匹配到易变的 EC */
		if (fc_cur_ec->ec_has_volatile)
			continue;
		/* 它也必须在语义上与外连接子句相匹配 */
		if (fc_collation != fc_cur_ec->ec_collation)
			continue;
		if (!equal(fc_rinfo->mergeopfamilies, fc_cur_ec->ec_opfamilies))
			continue;

		/*
		 * 它是否包含 COALESCE(leftvar, rightvar) 结构？
		 *
		 * 我们可以假设 COALESCE() 输入的顺序与连接子句相同，
		 * 因为在我们关心的情况下，二者都是自动生成的。
		 *
		 * XXX 目前，在跨类型情况中，这可能无法匹配，因为
		 * COALESCE 将包含类型转换操作，而连接子句可能没有
		 * （如果有可用于这两个列类型的交叉类型合并连接运算符）。从 
		 * COALESCE 参数中去掉隐式强制转换可以吗？
		 */
		fc_match = false;
		foreach(lc2, fc_cur_ec->ec_members)
		{
			fc_coal_em = (EquivalenceMember *) lfirst(lc2);
			Assert(!fc_coal_em->em_is_child);	/* 还没有子节点 */
			if (IsA(fc_coal_em->em_expr, CoalesceExpr))
			{
				CoalesceExpr *fc_cexpr = (CoalesceExpr *) fc_coal_em->em_expr;
				Node	   *fc_cfirst;
				Node	   *fc_csecond;

				if (list_length(fc_cexpr->args) != 2)
					continue;
				fc_cfirst = (Node *) linitial(fc_cexpr->args);
				fc_csecond = (Node *) lsecond(fc_cexpr->args);

				if (equal(fc_leftvar, fc_cfirst) && equal(fc_rightvar, fc_csecond))
				{
					fc_coal_idx = foreach_current_index(lc2);
					fc_match = true;
					break;
				}
			}
		}
		if (!fc_match)
			continue;			/* 无匹配，因此忽略此 EC */

		/*
		 * 是的，它确实包含！尝试为 EC 中的每个常量生成
		 * LEFTVAR = CONSTANT 和 RIGHTVAR = CONSTANT 子句。
		 * 请注意，在我们决定丢弃外连接子句之前，必须
		 * 至少对每个变量成功处理一个常量。
		 */
		fc_matchleft = fc_matchright = false;
		foreach(lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(lc2);
			Oid			fc_eq_op;
			RestrictInfo *fc_newrinfo;

			if (!fc_cur_em->em_is_const)
				continue;		/* 忽略非常量成员 */
			fc_eq_op = fc_select_equality_operator(fc_cur_ec,
											 fc_left_type,
											 fc_cur_em->em_datatype);
			if (OidIsValid(fc_eq_op))
			{
				fc_newrinfo = build_implied_join_equality(fc_root,
													   fc_eq_op,
													   fc_cur_ec->ec_collation,
													   fc_leftvar,
													   fc_cur_em->em_expr,
													   bms_copy(fc_left_relids),
													   bms_copy(fc_left_nullable_relids),
													   fc_cur_ec->ec_min_security);
				if (process_equivalence(fc_root, &fc_newrinfo, true))
					fc_matchleft = true;
			}
			fc_eq_op = fc_select_equality_operator(fc_cur_ec,
											 fc_right_type,
											 fc_cur_em->em_datatype);
			if (OidIsValid(fc_eq_op))
			{
				fc_newrinfo = build_implied_join_equality(fc_root,
													   fc_eq_op,
													   fc_cur_ec->ec_collation,
													   fc_rightvar,
													   fc_cur_em->em_expr,
													   bms_copy(fc_right_relids),
													   bms_copy(fc_right_nullable_relids),
													   fc_cur_ec->ec_min_security);
				if (process_equivalence(fc_root, &fc_newrinfo, true))
					fc_matchright = true;
			}
		}

		/*
		 * 如果我们能够将两个变量都与常量相等，那么我们完成了，
		 * 我们可以将全连接子句当作冗余丢弃。此外，我们
		 * 可以从 EC 中移除 COALESCE 条目，因为添加的
		 * 限制确保它将始终具有预期值。（我们不麻烦尝试
		 * 更新 ec_relids 或 ec_sources。）
		 */
		if (fc_matchleft && fc_matchright)
		{
			fc_cur_ec->ec_members = list_delete_nth_cell(fc_cur_ec->ec_members, fc_coal_idx);
			return true;
		}

		/*
		 * 否则，退出搜索循环，因为我们知道 COALESCE
		 * 至多出现在一个 EC 中（XXX 如果我们允许
		 * 上述强制转换的去除，可能不再成立？）
		 */
		break;
	}

	return false;				/* 未能做出任何推导 */
}


/*
 * exprs_known_equal
 *	  检测两个表达式是否由于等价关系而已知相等。
 *
 * 实际上，这只是表明这些表达式根据某些操作族的相等
 * 关系是相等的——但我们仅出于选择性估计而使用它，因此模糊的
 * 相等概念也是可以的。
 *
 * 注意：不管查看 "equal(item1, item2)"；如果可能
 * 传递相同的项目，调用者必须检查这种情况。
 */
bool exprs_known_equal(PlannerInfo *fc_root, Node *fc_item1, Node *fc_item2)
{
	ListCell   *fc_lc1;

	foreach(fc_lc1, fc_root->eq_classes)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) lfirst(fc_lc1);
		bool		fc_item1member = false;
		bool		fc_item2member = false;
		ListCell   *fc_lc2;

		/* 永远不要匹配到易变的 EC */
		if (fc_ec->ec_has_volatile)
			continue;

		foreach(fc_lc2, fc_ec->ec_members)
		{
			EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc2);

			if (fc_em->em_is_child)
				continue;		/* 在这里忽略子项 */
			if (equal(fc_item1, fc_em->em_expr))
				fc_item1member = true;
			else if (equal(fc_item2, fc_em->em_expr))
				fc_item2member = true;
			/* 一旦证明等价就退出 */
			if (fc_item1member && fc_item2member)
				return true;
		}
	}
	return false;
}


/*
 * match_eclasses_to_foreign_key_col
 *	  查看任何 eclass 是否证明了外键列匹配。
 *
 * 如果外键的 colno'th 列的引用变量和引用变量因任何
 * eclass 而已知相等，则返回该 eclass；否则返回 NULL。
 * （原则上，如果涉及多种排序规则，可能会有多个匹配的
 * eclass，但由于排序对于相等性并不重要，我们在此忽略
 * 这个细节。）这与 exprs_known_equal 类似，
 * 除了我们坚持比较运算符必须与 eclass 匹配，以便结果
 * 是明确的而不是近似的。
 *
 * 在成功时，我们还将 fkinfo->eclass[colno] 设置为匹配的
 * eclass，并将 fkinfo->fk_eclass_member[colno] 设置为
 * 引用变量的 eclass 成员。
 */
EquivalenceClass *
match_eclasses_to_foreign_key_col(PlannerInfo *fc_root,
								  ForeignKeyOptInfo *fc_fkinfo,
								  int fc_colno)
{
	Index		fc_var1varno = fc_fkinfo->con_relid;
	AttrNumber	fc_var1attno = fc_fkinfo->conkey[fc_colno];
	Index		fc_var2varno = fc_fkinfo->ref_relid;
	AttrNumber	fc_var2attno = fc_fkinfo->confkey[fc_colno];
	Oid			fc_eqop = fc_fkinfo->conpfeqop[fc_colno];
	RelOptInfo *fc_rel1 = fc_root->simple_rel_array[fc_var1varno];
	RelOptInfo *fc_rel2 = fc_root->simple_rel_array[fc_var2varno];
	List	   *fc_opfamilies = NIL;	/* 仅在需要时计算 */
	Bitmapset  *fc_matching_ecs;
	int			fc_i;

	/* 仅考虑提到两个关系的 eclasses */
	Assert(fc_root->ec_merging_done);
	Assert(IS_SIMPLE_REL(fc_rel1));
	Assert(IS_SIMPLE_REL(fc_rel2));
	fc_matching_ecs = bms_intersect(fc_rel1->eclass_indexes,
								 fc_rel2->eclass_indexes);

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_matching_ecs, fc_i)) >= 0)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes,
															 fc_i);
		EquivalenceMember *fc_item1_em = NULL;
		EquivalenceMember *fc_item2_em = NULL;
		ListCell   *fc_lc2;

		/* 永远不要匹配到易变的 EC */
		if (fc_ec->ec_has_volatile)
			continue;
		/* 注意：在这里匹配到“损坏”的 eclasses 似乎是可以的 */

		foreach(fc_lc2, fc_ec->ec_members)
		{
			EquivalenceMember *fc_em = (EquivalenceMember *) lfirst(fc_lc2);
			Var		   *fc_var;

			if (fc_em->em_is_child)
				continue;		/* 在这里忽略子项 */

			/* EM 必须是一个可能具有 RelabelType 的 Var */
			fc_var = (Var *) fc_em->em_expr;
			while (fc_var && IsA(fc_var, RelabelType))
				fc_var = (Var *) ((RelabelType *) fc_var)->arg;
			if (!(fc_var && IsA(fc_var, Var)))
				continue;

			/* 匹配? */
			if (fc_var->varno == fc_var1varno && fc_var->varattno == fc_var1attno)
				fc_item1_em = fc_em;
			else if (fc_var->varno == fc_var2varno && fc_var->varattno == fc_var2attno)
				fc_item2_em = fc_em;

			/* 我们是否在这个 EC 中找到 PK 和 FK 列？ */
			if (fc_item1_em && fc_item2_em)
			{
				/*
				 * 如果 eqop 与 EC 的操作族匹配则成功。我们可以在扫描
				 * 成员之前测试这一点，但测试成员匹配可能更便宜。
				 */
				if (fc_opfamilies == NIL)	/* 如果我们还没有计算则进行计算 */
					fc_opfamilies = get_mergejoin_opfamilies(fc_eqop);
				if (equal(fc_opfamilies, fc_ec->ec_opfamilies))
				{
					fc_fkinfo->eclass[fc_colno] = fc_ec;
					fc_fkinfo->fk_eclass_member[fc_colno] = fc_item2_em;
					return fc_ec;
				}
				/* 否则，完成这个 EC，继续下一个 */
				break;
			}
		}
	}
	return NULL;
}

/*
 * find_derived_clause_for_ec_member
 *	  搜索提到给定 EM 的先前派生子句。
 *
 * eclass 应该是一个 ec_has_const EC，其 EM 是一个非常量成员。
 * 这应该确保只有一个派生子句提到 EM
 * （并将其与常量相等）。
 * 如果找不到这样的子句，则返回 NULL。
 */
RestrictInfo * find_derived_clause_for_ec_member(EquivalenceClass *fc_ec,
								  EquivalenceMember *fc_em)
{
	ListCell   *fc_lc;

	Assert(fc_ec->ec_has_const);
	Assert(!fc_em->em_is_const);
	foreach(fc_lc, fc_ec->ec_derives)
	{
		RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc);

		/*
		 * generate_base_implied_equalities_const 会将非常量
		 * 成员放在派生子句的左侧。
		 */
		if (fc_rinfo->left_em == fc_em)
			return fc_rinfo;
	}
	return NULL;
}


/*
 * add_child_rel_equivalences
 *	  搜索引用 child_rel 根父级的 EC 成员，并
 *	  添加引用 child_rel 的变换成员。
 *
 * 注意，只有在我们至少有一些理由相信生成的 EC 成员
 * 会有用时，才会调用此函数。
 *
 * parent_rel 和 child_rel 可以从 appinfo 派生出，但由于
 * 调用者已经计算它们，我们可以直接将它们传递进来。
 *
 * 传入的 AppendRelInfo 在 parent_rel 不是顶级的
 * baserel 时不使用，因为它显示了从 parent_rel 的映射，但
 * 我们需要转换引用顶级父项的 EC 表达式。
 * 使用它比使用 adjust_appendrel_attrs_multilevel() 更快，
 * 因此我们在可以时优先使用它。
 */
void add_child_rel_equivalences(PlannerInfo *fc_root,
						   AppendRelInfo *fc_appinfo,
						   RelOptInfo *fc_parent_rel,
						   RelOptInfo *fc_child_rel)
{
	Relids		fc_top_parent_relids = fc_child_rel->top_parent_relids;
	Relids		fc_child_relids = fc_child_rel->relids;
	int			fc_i;

	
/*
	 * EC 合并应该已经完成，因此我们可以使用父关系的
	 * eclass_indexes 来避免搜索所有的 root->eq_classes。
	 */
	Assert(fc_root->ec_merging_done);
	Assert(IS_SIMPLE_REL(fc_parent_rel));

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_parent_rel->eclass_indexes, fc_i)) >= 0)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes, fc_i);
		int			fc_num_members;

		/*
		 * 如果这个 EC 包含一个易变表达式，那么生成子
		 * EM 将会非常危险，因此跳过它。我们依赖于一个
		 * 易变 EC 只有一个 EM。
		 */
		if (fc_cur_ec->ec_has_volatile)
			continue;

		/* 健康检查 eclass_indexes 仅包含父关系的 EC */
		Assert(bms_is_subset(fc_top_parent_relids, fc_cur_ec->ec_relids));

		/*
		 * 我们在这里不使用 foreach()，因为扫描
		 * 新添加的子成员没有意义，因此我们可以在
		 * 最后一个已存在的 EC 成员之后停止。
		 */
		fc_num_members = list_length(fc_cur_ec->ec_members);
		for (int fc_pos = 0; fc_pos < fc_num_members; fc_pos++)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) list_nth(fc_cur_ec->ec_members, fc_pos);

			if (fc_cur_em->em_is_const)
				continue;		/* 此处忽略常量 */

			/*
			 * 我们在这里只考虑原始的 EC 成员，而不是
			 * 已经转换过的子成员。否则，如果某个原始
			 * 成员表达式引用了多个附加关系，我们将得到
			 * 一个 O(N^2) 的无用派生表达式爆炸，针对
			 * 子级的组合。 （但 add_child_join_rel_equivalences
			 * 可能会为了分区连接的目的添加有针对性的组合。）
			 */
			if (fc_cur_em->em_is_child)
				continue;		/* 在这里忽略子项 */

			/* 这个成员是否引用了子级的最上层父关系？ */
			if (bms_overlap(fc_cur_em->em_relids, fc_top_parent_relids))
			{
				/* 是的，生成变换后的子版本 */
				Expr	   *fc_child_expr;
				Relids		fc_new_relids;
				Relids		fc_new_nullable_relids;

				if (fc_parent_rel->reloptkind == RELOPT_BASEREL)
				{
					/* 简单的单层变换 */
					fc_child_expr = (Expr *)
						adjust_appendrel_attrs(fc_root,
											   (Node *) fc_cur_em->em_expr,
											   1, &fc_appinfo);
				}
				else
				{
					/* 必须进行多层变换 */
					fc_child_expr = (Expr *)
						adjust_appendrel_attrs_multilevel(fc_root,
														  (Node *) fc_cur_em->em_expr,
														  fc_child_relids,
														  fc_top_parent_relids);
				}

				/*
				 * 转换 em_relids 以匹配。注意我们并不是在这里执行
				 * pull_varnos(child_expr)，例如变换可能替换了一个常量，但我们
				 * 不希望子成员被标记为常量。
				 */
				fc_new_relids = bms_difference(fc_cur_em->em_relids,
											fc_top_parent_relids);
				fc_new_relids = bms_add_members(fc_new_relids, fc_child_relids);

				/*
				 * 对于 nullable_relids 同样如此。注意这段代码假设
				 * 父级和子级 relids 是单个的。
				 */
				fc_new_nullable_relids = fc_cur_em->em_nullable_relids;
				if (bms_overlap(fc_new_nullable_relids, fc_top_parent_relids))
				{
					fc_new_nullable_relids = bms_difference(fc_new_nullable_relids,
														 fc_top_parent_relids);
					fc_new_nullable_relids = bms_add_members(fc_new_nullable_relids,
														  fc_child_relids);
				}

				(void) add_eq_member(fc_cur_ec, fc_child_expr,
									 fc_new_relids, fc_new_nullable_relids,
									 true, fc_cur_em->em_datatype);

				/* 记录该 EC 索引以用于子关系 */
				fc_child_rel->eclass_indexes = bms_add_member(fc_child_rel->eclass_indexes, fc_i);
			}
		}
	}
}

/*
 * add_child_join_rel_equivalences
 *	  类似于 add_child_rel_equivalences()，但用于 joinrels
 *
 * 在这里，我们查找与顶级父级 joinrel 相关的 EC，并添加
 * 引用此子 joinrel 的变换成员表达式。
 *
 * 注意这个函数不会被调用，除非我们至少有一些理由相信
 * 它生成的 EC 成员是有用的。
 */
void add_child_join_rel_equivalences(PlannerInfo *fc_root,
								int fc_nappinfos, AppendRelInfo **fc_appinfos,
								RelOptInfo *fc_parent_joinrel,
								RelOptInfo *fc_child_joinrel)
{
	Relids		fc_top_parent_relids = fc_child_joinrel->top_parent_relids;
	Relids		fc_child_relids = fc_child_joinrel->relids;
	Bitmapset  *fc_matching_ecs;
	MemoryContext fc_oldcontext;
	int			fc_i;

	Assert(IS_JOIN_REL(fc_child_joinrel) && IS_JOIN_REL(fc_parent_joinrel));

	/* 我们只需要考虑提到父级 joinrel 的 EC */
	fc_matching_ecs = get_eclass_indexes_for_relids(fc_root, fc_top_parent_relids);

	/*
	 * 如果在 GEQO 连接规划过程中被调用，我们仍然必须在主
	 * 规划上下文中创建任何新的 EC 成员，以避免在 GEQO 上下文重置后
	 * 出现损坏的 EC 数据结构。这是个问题，因为我们在重复的 GEQO 周期
	 * 中会泄漏内存。不过现在，膨胀总比崩溃好。如果这成为一个真正的问题，
	 * 我们需要采取措施以避免生成重复的 EC 成员。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_root->planner_cxt);

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_matching_ecs, fc_i)) >= 0)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes, fc_i);
		int			fc_num_members;

		/*
		 * 如果这个 EC 包含一个易变表达式，那么生成子
		 * EM 将会非常危险，因此跳过它。我们依赖于一个
		 * 易变 EC 只有一个 EM。
		 */
		if (fc_cur_ec->ec_has_volatile)
			continue;

		/* 对 get_eclass_indexes_for_relids 结果的合理性检查 */
		Assert(bms_overlap(fc_top_parent_relids, fc_cur_ec->ec_relids));

		/*
		 * 我们在这里不使用 foreach()，因为扫描
		 * 新添加的子成员没有意义，因此我们可以在
		 * 最后一个已存在的 EC 成员之后停止。
		 */
		fc_num_members = list_length(fc_cur_ec->ec_members);
		for (int fc_pos = 0; fc_pos < fc_num_members; fc_pos++)
		{
			EquivalenceMember *fc_cur_em = (EquivalenceMember *) list_nth(fc_cur_ec->ec_members, fc_pos);

			if (fc_cur_em->em_is_const)
				continue;		/* 此处忽略常量 */

			/*
			 * 我们只考虑原始的 EC 成员，而不是已经变换的子成员。
			 */
			if (fc_cur_em->em_is_child)
				continue;		/* 在这里忽略子项 */

			/*
			 * 我们可以忽略引用单个基关系的表达式，
			 * 因为 add_child_rel_equivalences 应该已经处理了它们。
			 */
			if (bms_membership(fc_cur_em->em_relids) != BMS_MULTIPLE)
				continue;

			/* 这个成员是否引用了子级的最上层父关系？ */
			if (bms_overlap(fc_cur_em->em_relids, fc_top_parent_relids))
			{
				/* 是的，生成变换后的子版本 */
				Expr	   *fc_child_expr;
				Relids		fc_new_relids;
				Relids		fc_new_nullable_relids;

				if (fc_parent_joinrel->reloptkind == RELOPT_JOINREL)
				{
					/* 简单的单层变换 */
					fc_child_expr = (Expr *)
						adjust_appendrel_attrs(fc_root,
											   (Node *) fc_cur_em->em_expr,
											   fc_nappinfos, fc_appinfos);
				}
				else
				{
					/* 必须进行多层变换 */
					Assert(fc_parent_joinrel->reloptkind == RELOPT_OTHER_JOINREL);
					fc_child_expr = (Expr *)
						adjust_appendrel_attrs_multilevel(fc_root,
														  (Node *) fc_cur_em->em_expr,
														  fc_child_relids,
														  fc_top_parent_relids);
				}

				/*
				 * 转换 em_relids 以匹配。注意我们并不是在这里执行
				 * pull_varnos(child_expr)，例如变换可能替换了一个常量，但我们
				 * 不希望子成员被标记为常量。
				 */
				fc_new_relids = bms_difference(fc_cur_em->em_relids,
											fc_top_parent_relids);
				fc_new_relids = bms_add_members(fc_new_relids, fc_child_relids);

				/*
				 * 对于 nullable_relids，我们必须选择性地用子级的
				 * nullable relids 替换父级的。
				 */
				fc_new_nullable_relids = fc_cur_em->em_nullable_relids;
				if (bms_overlap(fc_new_nullable_relids, fc_top_parent_relids))
					fc_new_nullable_relids =
						adjust_child_relids_multilevel(fc_root,
													   fc_new_nullable_relids,
													   fc_child_relids,
													   fc_top_parent_relids);

				(void) add_eq_member(fc_cur_ec, fc_child_expr,
									 fc_new_relids, fc_new_nullable_relids,
									 true, fc_cur_em->em_datatype);
			}
		}
	}

	MemoryContextSwitchTo(fc_oldcontext);
}


/*
 * generate_implied_equalities_for_column
 *	  创建可与特定列一起使用的 EC 派生 joinclauses。
 *
 * 这由 indxpath.c 使用，以从 EC 中提取潜在的可索引 joinclauses，
 * 并且可以被外部数据包装器用于类似目的。
 * 我们假设仅单表中的 Vars 中的表达式才感兴趣，
 * 但调用者提供一个回调函数以准确识别希望了解的具体表达式。
 *
 * 我们假设给定的表/索引列仅能出现在一个 EC 中。
 * （这在绝大多数情况下都应该成立，如果不成立，
 * 我们在第一个匹配时就停止。）因此，我们返回的
 * 是一个冗余的条款列表，将表/索引列与已知相等的其他关系值对应起来。
 * 这些条款中的任何一个都可以用于创建参数化路径，
 * 而使用多个条款是没有价值的。（但为每个条款创建
 * 单独的参数化路径是值得的，因为这样可以导致不同的
 * 连接顺序。）
 *
 * 调用者可以传递一个不感兴趣于连接的 Relids 集合，
 * 以节省创建无用条款的工作。
 */
List * generate_implied_equalities_for_column(PlannerInfo *fc_root,
									   RelOptInfo *fc_rel,
									   ec_matches_callback_type fc_callback,
									   void *fc_callback_arg,
									   Relids fc_prohibited_rels)
{
	List	   *fc_result = NIL;
	bool		fc_is_child_rel = (fc_rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
	Relids		fc_parent_relids;
	int			fc_i;

	/* 依赖于 eclass_indexes 应该是可以的 */
	Assert(fc_root->ec_merging_done);

	/* 索引仅在基关系或“其他”成员关系上可用。 */
	Assert(IS_SIMPLE_REL(fc_rel));

	/* 如果是子关系，我们需要知道它的父级是什么 */
	if (fc_is_child_rel)
		fc_parent_relids = find_childrel_parents(fc_root, fc_rel);
	else
		fc_parent_relids = NULL;	/* 未使用，但保持编译器安静 */

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_rel->eclass_indexes, fc_i)) >= 0)
	{
		EquivalenceClass *fc_cur_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes, fc_i);
		EquivalenceMember *fc_cur_em;
		ListCell   *fc_lc2;

		/* 合理性检查 eclass_indexes 仅包含该关系的 EC */
		Assert(fc_is_child_rel || bms_is_subset(fc_rel->relids, fc_cur_ec->ec_relids));

		/*
		 * 如果是常量或单成员，将不会生成 joinclauses（后者
		 * 测试也涵盖了波动情况）
		 */
		if (fc_cur_ec->ec_has_const || list_length(fc_cur_ec->ec_members) <= 1)
			continue;

		/*
		 * 扫描成员，寻找与目标列的匹配。注意子级 EC 成员被考虑，
		 * 但只有在它们属于目标关系时。（与常规成员不同，
		 * 相同的表达式可以是多个 EC 的子成员。因此，
		 * 子关系的目标列应该匹配哪个 EC 是有序依赖的。
		 * 这是令人烦恼的，但它仅在角落案例中发生，因此我们
		 * 暂时接受仅报告第一个匹配。另请参见 get_eclass_for_sort_expr。）
		 */
		fc_cur_em = NULL;
		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			fc_cur_em = (EquivalenceMember *) lfirst(fc_lc2);
			if (bms_equal(fc_cur_em->em_relids, fc_rel->relids) &&
				fc_callback(fc_root, fc_rel, fc_cur_ec, fc_cur_em, fc_callback_arg))
				break;
			fc_cur_em = NULL;
		}

		if (!fc_cur_em)
			continue;

		/*
		 * 找到匹配。扫描其他 EC 成员并尝试生成
		 * joinclauses。
		 */
		foreach(fc_lc2, fc_cur_ec->ec_members)
		{
			EquivalenceMember *fc_other_em = (EquivalenceMember *) lfirst(fc_lc2);
			Oid			fc_eq_op;
			RestrictInfo *fc_rinfo;

			if (fc_other_em->em_is_child)
				continue;		/* 在这里忽略子项 */

			/* 确保它将是与不同关系的连接 */
			if (fc_other_em == fc_cur_em ||
				bms_overlap(fc_other_em->em_relids, fc_rel->relids))
				continue;

			/* 如果调用者不希望与此关系连接，则忽略它 */
			if (bms_overlap(fc_other_em->em_relids, fc_prohibited_rels))
				continue;

			/*
			 * 此外，如果这是一个子关系，避免生成无用的连接
			 * 到它的父关系。
			 */
			if (fc_is_child_rel &&
				bms_overlap(fc_parent_relids, fc_other_em->em_relids))
				continue;

			fc_eq_op = fc_select_equality_operator(fc_cur_ec,
											 fc_cur_em->em_datatype,
											 fc_other_em->em_datatype);
			if (!OidIsValid(fc_eq_op))
				continue;

			/* 设置 parent_ec 以标记为与其他 joinclauses 冗余 */
			fc_rinfo = fc_create_join_clause(fc_root, fc_cur_ec, fc_eq_op,
									   fc_cur_em, fc_other_em,
									   fc_cur_ec);

			fc_result = lappend(fc_result, fc_rinfo);
		}

		/*
		 * 如果在某种情况下我们未能创建任何连接条款，我们不妨
		 * 继续扫描 EC 寻找其他匹配。但是如果我们确实创建了任何，
		 * 我们就完成了，因为我们不希望返回非冗余的条款。
		 */
		if (fc_result)
			break;
	}

	return fc_result;
}


/*
 * have_relevant_eclass_joinclause
 *		检测是否存在一个 EquivalenceClass 可以产生
 *		涉及两个给定关系的连接条件。
 *
 * 这本质上是一个非常简化的版本
 * generate_join_implied_equalities()。注意偶尔说“不”是可以的。
 * 因此我们不去考虑一些细节，比如缺少跨类型操作符是否会妨碍
 * 条件的实际生成。
 */
bool have_relevant_eclass_joinclause(PlannerInfo *fc_root,
								RelOptInfo *fc_rel1, RelOptInfo *fc_rel2)
{
	Bitmapset  *fc_matching_ecs;
	int			fc_i;

	/* 仅检查同时提到 rel1 和 rel2 的 eclasses */
	fc_matching_ecs = get_common_eclass_indexes(fc_root, fc_rel1->relids,
											 fc_rel2->relids);

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_matching_ecs, fc_i)) >= 0)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes,
															 fc_i);

		/*
		 * 基本检查 get_common_eclass_indexes 是否仅给出了包含
		 * 两个关系的 ECs。
		 */
		Assert(bms_overlap(fc_rel1->relids, fc_ec->ec_relids));
		Assert(bms_overlap(fc_rel2->relids, fc_ec->ec_relids));

		/*
		 * 如果是单一成员将不会生成连接子句（此测试也覆盖了
		 * 不稳定的情况）
		 */
		if (list_length(fc_ec->ec_members) <= 1)
			continue;

		/*
		 * 我们不需要检查等同性类的个别成员，因为
		 * 我们关心的只是每个关系是否与至少一个成员的 relids 重叠，而
		 * get_common_eclass_indexes() 和上面的单一成员检查就足以证明这一点。
		 * （与 have_relevant_joinclause() 一样，EC 不必能够
		 * 形成确切关联给定关系的连接子句，只需能够形成提及两者的连接子句，
		 * 如果它们都重叠 ec_relids，这一点肯定是成立的。）
		 *
		 * 注意，我们没有测试 ec_broken；如果我们测试了，就需要一个单独的代码
		 * 路径来查看 ec_sources。无论如何检查成员也可以作为一个可能过于乐观的启发式。
		 *
		 * 我们也没有测试 ec_has_const，尽管常量等同性类不会
		 * 生成真实的连接子句。这是因为如果我们有 "WHERE a.x =
		 * b.y 和 a.x = 42"，考虑 a 和 b 之间的连接是值得的，
		 * 因为尽管结果最终将是一个没有限定条件的嵌套循环，但连接结果可能会很小。
		 */

		return true;
	}

	return false;
}


/*
 * has_relevant_eclass_joinclause
 *		检测是否存在一个等同性类可以生成
 *		涉及给定关系和其他任何东西的连接子句。
 *
 * 这与 have_relevant_eclass_joinclause 相同，另一个关系
 * 隐含定义为“查询中的其他所有内容”。
 */
bool has_relevant_eclass_joinclause(PlannerInfo *fc_root, RelOptInfo *fc_rel1)
{
	Bitmapset  *fc_matched_ecs;
	int			fc_i;

	/* 仅检查提及 rel1 的等同性类 */
	fc_matched_ecs = get_eclass_indexes_for_relids(fc_root, fc_rel1->relids);

	fc_i = -1;
	while ((fc_i = bms_next_member(fc_matched_ecs, fc_i)) >= 0)
	{
		EquivalenceClass *fc_ec = (EquivalenceClass *) list_nth(fc_root->eq_classes,
															 fc_i);

		/*
		 * 如果是单一成员将不会生成连接子句（此测试也覆盖了
		 * 不稳定的情况）
		 */
		if (list_length(fc_ec->ec_members) <= 1)
			continue;

		/*
		 * 根据 have_relevant_eclass_joinclause 中的注释，找到一个
		 * 提及这个关系和其他某个关系的 EC 就足够了。
		 */
		if (!bms_is_subset(fc_ec->ec_relids, fc_rel1->relids))
			return true;
	}

	return false;
}


/*
 * eclass_useful_for_merging
 *	  检测 EC 是否可以生成与指定关系相关的任何可合并连接子句。
 *
 * 这只是一个启发式测试，不必准确；说 "是" 错误
 * 总比说 "不是" 好。因此，我们不考虑缺少交叉类型运算符
 * 是否可能会阻止子句的实际生成。
 */
bool eclass_useful_for_merging(PlannerInfo *fc_root,
						  EquivalenceClass *fc_eclass,
						  RelOptInfo *fc_rel)
{
	Relids		fc_relids;
	ListCell   *fc_lc;

	Assert(!fc_eclass->ec_merged);

	/*
	 * 如果是常量或单一成员将不会生成连接子句（后者测试
	 * 也覆盖了不稳定的情况）
	 */
	if (fc_eclass->ec_has_const || list_length(fc_eclass->ec_members) <= 1)
		return false;

	/*
	 * 注意，我们没有测试 ec_broken；如果我们测试了，就需要一个单独的代码路径
	 * 来查看 ec_sources。无论如何检查成员也可以作为一个
	 * 可能过于乐观的启发式。
	 */

	/* 如果指定关系是子关系，我们必须考虑最上层的父关系 */
	if (IS_OTHER_REL(fc_rel))
	{
		Assert(!bms_is_empty(fc_rel->top_parent_relids));
		fc_relids = fc_rel->top_parent_relids;
	}
	else
		fc_relids = fc_rel->relids;

	/* 如果关系已经包含了等同性类的所有成员，就没有必要搜索 */
	if (bms_is_subset(fc_eclass->ec_relids, fc_relids))
		return false;

	/* 要连接，我们需要一个不在给定关系中的成员 */
	foreach(fc_lc, fc_eclass->ec_members)
	{
		EquivalenceMember *fc_cur_em = (EquivalenceMember *) lfirst(fc_lc);

		if (fc_cur_em->em_is_child)
			continue;			/* 在这里忽略子项 */

		if (!bms_overlap(fc_cur_em->em_relids, fc_relids))
			return true;
	}

	return false;
}


/*
 * is_redundant_derived_clause
 *		测试 rinfo 是否源自与 clauselist 中的任何子句相同的 EC；
 *		如果是，则可以推断它代表与列表中该成员的条件冗余。
 */
bool is_redundant_derived_clause(RestrictInfo *fc_rinfo, List *fc_clauselist)
{
	EquivalenceClass *fc_parent_ec = fc_rinfo->parent_ec;
	ListCell   *fc_lc;

	/* 如果它不是来自某个 EC 的潜在冗余子句则失败 */
	if (fc_parent_ec == NULL)
		return false;

	foreach(fc_lc, fc_clauselist)
	{
		RestrictInfo *fc_otherrinfo = (RestrictInfo *) lfirst(fc_lc);

		if (fc_otherrinfo->parent_ec == fc_parent_ec)
			return true;
	}

	return false;
}

/*
 * is_redundant_with_indexclauses
 *		测试 rinfo 是否与 IndexClause 列表中的任何子句冗余。
 *		在这里，为了方便，我们测试简单的相等性以及它是否来自与列表中
 *		任何成员相同的 EC。
 */
bool is_redundant_with_indexclauses(RestrictInfo *fc_rinfo, List *fc_indexclauses)
{
	EquivalenceClass *fc_parent_ec = fc_rinfo->parent_ec;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_indexclauses)
	{
		IndexClause *fc_iclause = lfirst_node(IndexClause, fc_lc);
		RestrictInfo *fc_otherrinfo = fc_iclause->rinfo;

		/* 如果 indexclause 是有损的，它不会准确地强制条件 */
		if (fc_iclause->lossy)
			continue;

		/* 如果是相同的子句则匹配（指针相等应该足够） */
		if (fc_rinfo == fc_otherrinfo)
			return true;
		/* 如果来自相同的 EC 则匹配 */
		if (fc_parent_ec && fc_otherrinfo->parent_ec == fc_parent_ec)
			return true;

		/*
		 * 无需查看 iclause->indexquals 中的派生子句；
		 * 如果父子句不匹配，它们也不能匹配。
		 */
	}

	return false;
}

/*
 * get_eclass_indexes_for_relids
 *		构建并返回一个 Bitmapset，包含根的
 *		eq_classes 列表中提及这些 relids 的所有等同性类的索引。
 */
static Bitmapset * get_eclass_indexes_for_relids(PlannerInfo *fc_root, Relids fc_relids)
{
	Bitmapset  *fc_ec_indexes = NULL;
	int			fc_i = -1;

	/* 依赖于 eclass_indexes 应该是可以的 */
	Assert(fc_root->ec_merging_done);

	while ((fc_i = bms_next_member(fc_relids, fc_i)) > 0)
	{
		RelOptInfo *fc_rel = fc_root->simple_rel_array[fc_i];

		fc_ec_indexes = bms_add_members(fc_ec_indexes, fc_rel->eclass_indexes);
	}
	return fc_ec_indexes;
}

/*
 * get_common_eclass_indexes
 *		构建并返回一个 Bitmapset，包含根的
 *		eq_classes 列表中提及 relids1 和 relids2 中的关系的所有等同性类的索引。
 */
static Bitmapset * get_common_eclass_indexes(PlannerInfo *fc_root, Relids fc_relids1, Relids fc_relids2)
{
	Bitmapset  *fc_rel1ecs;
	Bitmapset  *fc_rel2ecs;
	int			fc_relid;

	fc_rel1ecs = get_eclass_indexes_for_relids(fc_root, fc_relids1);

	/*
	 * 当 relids2 是一个单例集合时，我们可以直接使用关系的 eclass_indexes。
	 */
	if (bms_get_singleton_member(fc_relids2, &fc_relid))
		fc_rel2ecs = fc_root->simple_rel_array[fc_relid]->eclass_indexes;
	else
		fc_rel2ecs = get_eclass_indexes_for_relids(fc_root, fc_relids2);

	/* 计算并返回共同的 EC 索引，重用左侧输入。 */
	return bms_int_members(fc_rel1ecs, fc_rel2ecs);
}
